Mini Shell

Direktori : /lib64/gnome-shell/
Upload File :
Current File : //lib64/gnome-shell/libgnome-shell.so

ELF>P�@p�%@8	@�Y%�Y% b%bEbE�.x2 l%lElE��888$$�Y%�Y%�Y%  S�td�Y%�Y%�Y%  P�td`�$`�$`�$��Q�tdR�tdb%bEbE..GNU3o�kLҞ���q@��+�B+h�y i��H���P�#�(��HQ��BB��DRL����8C
�P � d@H�@
B��  4��T�H`h �Ra���"��@�e�#AB��0��	Hc	@�U�	 �Fk4  � H0 # ,��� ��$�4�!�@!�	�@ 0��@�2�1T% 84�T�9eX�(HAVf+��yz|}������������������������������������������������������������������������������	 !%'(*./15679:<=>?@BCFGILOQSVWYZ^_`bcefhikmpstuvwy{'Z���_�σͦ4�\z+���\yO�qe��޹��T�"y�|>齿�Ut�x���G�8R��0���,�̢����n"�w�ͅqyS$87.�D�|_�op��z��0�����$��(�Jz]�_:��<z�	Sp����"�m��9a8$��LRo�a5@���� /�϶R����v���{F!�qՌ$�A��_Bt��p,6YB
��)G��$��|)�!X��������ߝ/ӌ�t����(�˛pE���ϵ�h͕�=tf���l�{8��g��t��NG��L)?�_��)[���&8�77)^�C���*@�.��-:$��%�n?`�Z���6|j:6�1�ע	�f�Q�������Ji��r~q˲'����C�p��s��C
��^�9BmS;���i�{���"e��~��y�	�\k�s�	y�N>�ni�~�gC�B�ϩK�ą��몭07�aMC>�kJ��^BE���n��[9��5���0
�kJ΂�{�c�c1'aG�D��j_���a^�6¨}E����kJ�h�V���������X�6zrk�B��c>�lw�����֥�W�8�2��H�~p�箏�3veQ���,��w�_Q���X��}b����]ӆ4Ⱦߐ�h<�^u�Ӛ�ܝ�;�}x��˘
|������X�PA�.G�9;���!�;�zTs��v��C�	;χ�����&��4��҄4H'��)�u�W�����x�ܠ�a�k��7���ku�@�3���uJ���4���{.���t�[����f��4����B���.���Ǥ~	�ܪ@���
ӢI7�hv�>զ��j�B�W�I/B����W�v\�\
#����qX\��UW�)��F@�\-�,��Q��L��|(���U@
_g�=�J��(��Ǣ�K�vDϣ��?B6A�M�#�x���iO����<�0.�^(��d&�ßx�߂�q�}�=�C��臿F���x�=*��(=$7�t��.;[\�9�X�h
f/>+/�_�,�q�.lV�NMNe|
�b�%�;�!!;!h� 6l�gag��?�[ZHL
qalk@f�A�_�X4&�8 \�V�a<g-�I�(��)�m��e+�b�;k7,j"XP�k��y/�H�zf�f�8=�,"�/�,�k^�Fm�Tr>�i��O7PMP�\[;"OM�S�E'OT�	'I�dc|\�]�]�b,_�\Uh�,fA��`|V��#�^O7,F�+�'`?	c�"m8�b@;&?�QJL�9P]H�-a/�=8w=�~MJ/g*�a�b\7��(%3�Qv
ALjoFG�_(0bgM�F"�K�K�)mMBGFt:<7G?�Q�_�&Sl�[�P+��^RF�=�h��I�V�	-�B_\zj�/&�*dSv�]��i�V�G�f:"2�g<h1Up$_O4G�N�D�j�2	"0W�,�FrE[z�&,�C{H�P#T�g]2�X%;�O�2�6jq%eu��hABK4wW@e6T�$�lc^~N�U�"�Y�&�*23�1 �=XE;�:w3I1�PC&*f�=�"D�GtU^I�'�}-Y��C>R�LTh�eoD/�R�Z�/-?�G�13$6�;3'�"��lk,�O�AH(FcF'-)B$��WF%� 7�DvkGY?kBe�Y�F�k��)0C�iOG�`�:�% 1��;�U�*�`�J3�NJ73�P2�(�e�F��1�?>H0DU�j�3�
N�
T8��[�Xk`�2y 8�j�A_1al�%V�.(<d�8p�c� 
3m1v+@�Wh
�;���E�Sm]`
 X�)`�\�"�^��H�aCQ�,�f>�,��N��P�Y$j?*�"P_Zfl��6�)�X,�BI4J,
Y,L��2
*|I|lWd�G�:9u�8WxAF_�[/�
�`TY�{�!�)��M�!�/�"5+%��#c%��7(i�=z7�C�?x(N�8D"*w%�4�K�.]�D5�	�W�^�.Zb�9&Dbj)3�)X:
oJ��M4$^ob[��2mQ$&3�+(�X�eU*�!y[�R.<]\YO��h�.[a�c�d�d�.C/�`�E�	�M�3mK�$�g�D�
0`�^C\9N�KXf�
�%9
��7� �80�� K�$'"
9^ZSg!j��\k�&T��U�hjd�I�aVQ+G�6�fi�)8!P�4�2�e�
�dY�`�f.�0�#9F�fW�j'gE$�dk�..Q�/}?�B�0��M�Z*<?I:5:�J2B�K,S�2D�(�k�+�39Im�krX>��;�5�B�V�$`�I�+$�?�jnfpP4c�+FK��)`�-E�Klr9�hx&�W*M�P�$l�\�6�]�2.*�-�&�6�2�B�U�K�3uT3�lL�Ke|�d-F+�
Y
 &ee@-� ��+og#�Ro$�A�c@+�2�*c&D6:O�&�19Ekbx(�m�b`7`�O�i�:XX�b�gy*�R4U`�h �e�*�`FT�\LT-vE t!�J5E�l�,�g�+u<1Q$2m?H
i�4���0�L��,�l[dn0
>�E�
;X])Tk�91I�8�-`��
97F�!7A�Ei�)�]DG�;r�R0[nX��X�f��$�Q�[ 3H9nhdm�L~, B
OCg�0�9g�L9Y��O�g�A�^�3�8�'h�.�]B�XaL�'P �G2#�l�_��6<�?�A�i��!a�Z�����Y�
�p09h�a@l	�$�4�mMU0�dA�Q ����E���o��l��@@�q<C�*��*wm��
��NW���k�z��7�u�JP�pO��i.7 u��I �l%V�: �'�:3 l�6pr����	�a	�8��[B��"J�lZ�
��v7�$�'F�5�I���"�M����(I]�T����C��9�4`nV��
KS���NA���0��^0
gc�?��R�|�cNn\�< �,}0`_$��@0�E�H�A@p�A��e?�f�U �S[!�V�yT���m�V��5pq�/��aP4$P=P���D�I�a�4|>��SD��{Y�
G@��C<����. a�O'PC�!�*we�O��? �i��
9�����v� �d�Gk}$ 4kp�� �=a�`��h�e���a�`
9>����@"���9�!��Z

�0�d�����pd	z0%`>S���g %I���Fmp�g_jPk?!�U\KV Wv^���<0�LJp���0��� 0*R#p.��4�mz�a	@�J�Z

�o��E^#01��<��Q%$�3��%�90@��k�P��K=0��
�gQ@
�1��&	�Q�i^�M���@P�K��'m4�m	U��� ��ap2��!s�Tp�PR��KN�g��0�f�`5@q�U��e�>�B.�_!1:���F2 i.T���#�1���	o�u*��gR��I��r�c�DkU��v�S����p'^�#�1Ph	7�
�m'Dc@)Kf�>�#��&�=�01�gj�Z�
bP�f@Z�
tW�)�P$�$�4%)`J��c�@<�O��g@Ъ!A��8��s2�it�T��.\T��3_PI�5�q��2��c�Cc�?@��R" VQ������	.�^
�C���H`�Sn)pK�H����S��o�d`JY�@�#B#�0s�'�D9�@����<P�Q�`�"�I��l�0�0�fb5a�-��G�7v"�p���#�2;�Pp|��5`q�o��E9`�bgW�#	01i0�]��
�#1�V���x�E�>�����vY9�z��0��"R@���
p���S���N0d��_�$h��4�9�ibC@�Rd.`� �&�bp6|	��%�_�=�];p�1 
�B���0iW(0H��J0��`h)�-[�D	��	@���=��__gmon_start___ITM_deregisterTMCloneTable_ITM_registerTMCloneTable__cxa_finalizeshell_action_mode_get_typeg_once_init_enterg_intern_static_stringg_flags_register_staticg_once_init_leaveshell_app_state_get_typeg_enum_register_staticshell_snippet_hook_get_typeshell_network_agent_response_get_typeg_static_resource_get_resourceg_static_resource_initg_static_resource_finig_dbus_method_invocation_get_typeg_cclosure_marshal_genericg_signal_accumulator_true_handledg_signal_newg_strv_get_typeg_param_spec_booleang_object_interface_install_propertyg_value_unsetg_assertion_message_exprg_variant_type_checked_g_dbus_gvalue_to_gvariantg_variant_newg_dbus_proxy_get_typeg_type_check_instance_castg_dbus_proxy_callg_variant_unrefg_dbus_proxy_call_finishg_quark_to_stringg_logg_error_free__stack_chk_failg_mutex_initg_main_context_ref_thread_defaultg_malloc0g_value_initg_value_get_variantg_variant_equalg_value_get_ucharg_value_get_int64g_value_get_uint64g_value_get_doubleg_value_get_stringg_strcmp0g_value_get_intg_value_get_uintg_value_get_booleang_type_nameg_value_get_boxedg_strv_lengthg_dbus_proxy_get_cached_propertyg_dbus_gvariant_to_gvalueg_value_set_variantshell_org_gtk_application_interface_infog_dbus_proxy_set_interface_infoshell_org_gtk_application_override_propertiesg_object_class_override_propertyg_type_class_peek_parentg_type_check_class_castg_type_class_adjust_private_offsetg_dbus_interface_skeleton_get_typeshell_org_gtk_application_get_typeg_type_register_static_simpleg_type_interface_add_prerequisiteg_dbus_interface_info_lookup_signalg_variant_n_childreng_malloc0_ng_value_set_objectg_variant_iter_initg_variant_iter_next_valueg_signal_lookupg_signal_emitvshell_org_gtk_application_get_busyg_type_interface_peekshell_org_gtk_application_set_busyg_object_setshell_org_gtk_application_call_activateshell_org_gtk_application_call_activate_finishg_variant_getshell_org_gtk_application_call_activate_syncg_dbus_proxy_call_syncshell_org_gtk_application_call_openshell_org_gtk_application_call_open_finishshell_org_gtk_application_call_open_syncshell_org_gtk_application_call_command_lineshell_org_gtk_application_call_command_line_finishshell_org_gtk_application_call_command_line_syncshell_org_gtk_application_complete_activateg_dbus_method_invocation_return_valueshell_org_gtk_application_complete_openshell_org_gtk_application_complete_command_lineshell_org_gtk_application_proxy_get_typeg_type_add_instance_privateg_type_add_interface_staticg_variant_get_booleang_dbus_interface_info_lookup_propertyg_quark_try_stringg_datalist_id_set_data_fullg_variant_iter_nextg_variant_iter_freeg_object_notifyg_datalist_clearshell_org_gtk_application_proxy_newg_async_initable_new_asyncshell_org_gtk_application_proxy_new_finishg_async_result_get_source_objectg_async_initable_get_typeg_async_initable_new_finishg_object_unrefshell_org_gtk_application_proxy_new_syncg_initable_newshell_org_gtk_application_proxy_new_for_busshell_org_gtk_application_proxy_new_for_bus_finishshell_org_gtk_application_proxy_new_for_bus_syncshell_org_gtk_application_skeleton_get_typeg_mutex_lockg_mutex_unlockg_object_class_find_propertyg_object_set_propertyg_dbus_error_quarkg_set_errorg_object_get_propertyg_dbus_method_invocation_get_method_infog_dbus_method_invocation_return_errorg_unix_fd_list_get_typeg_dbus_method_invocation_get_messageg_dbus_message_get_unix_fd_listg_variant_builder_initg_variant_builder_addg_variant_ref_sinkg_dbus_interface_skeleton_get_connectionsg_dbus_interface_skeleton_get_object_pathg_dbus_connection_emit_signalg_list_free_fullg_variant_builder_clearg_source_destroyg_dbus_interface_skeleton_get_connectiong_variant_take_refg_variant_builder_endg_idle_source_newg_source_set_priorityg_object_refg_source_set_callbackg_source_set_nameg_source_attachg_source_unrefg_object_freeze_notifyg_object_thaw_notifyg_value_copyg_object_notify_by_pspecg_list_prependg_main_context_unrefg_mutex_clearshell_org_gtk_application_skeleton_newg_object_newshell_global_getg_object_get_shell_wm_create_inhibit_shortcuts_dialog_shell_wm_create_close_dialog_shell_wm_confirm_display_change_shell_wm_filter_keybinding_shell_wm_show_window_menu_for_rect_shell_wm_show_window_menu_shell_wm_hide_tile_preview_shell_wm_show_tile_preview_shell_wm_kill_switch_workspace_shell_wm_kill_window_effects_shell_wm_switch_workspace_shell_wm_destroy_shell_wm_size_change_shell_wm_size_changed_shell_wm_unminimize_shell_wm_minimize_shell_wm_mapmeta_plugin_get_typegnome_shell_plugin_get_typeclutter_get_default_backendclutter_backend_get_cogl_contextcogl_context_get_displaycogl_display_get_renderercogl_renderer_get_winsys_idshell_perf_log_get_defaultshell_perf_log_define_event_shell_global_set_plugin_shell_global_get_gjs_contextgjs_context_evalclutter_x11_get_default_displaycogl_get_proc_addressXDefaultScreenstrstrexitmeta_window_get_user_timeshell_global_get_displaymeta_display_get_workspace_managermeta_workspace_manager_get_active_workspacemeta_window_showing_on_its_workspaceg_spawn_close_pidg_child_watch_addsd_journal_stream_fddup2__errno_locationmeta_window_get_workspaceg_slist_prependg_signal_emitshell_app_system_get_default_shell_app_system_notify_app_state_changedg_return_if_fail_warningmeta_window_is_skip_taskbarshell_app_get_typeg_param_spec_enumg_object_class_install_propertyg_param_spec_stringg_action_group_get_typeg_param_spec_objectg_desktop_app_info_get_typeg_type_check_instance_is_ag_signal_connect_datashell_app_get_idg_app_info_get_typeg_app_info_get_idshell_app_create_icon_texturest_icon_newst_icon_get_typest_icon_set_icon_sizest_icon_set_fallback_icon_nameg_app_info_get_iconst_icon_set_giconshell_global_get_stagest_theme_context_get_for_stagest_texture_cache_get_defaultst_texture_cache_bind_cairo_surface_propertyst_widget_add_style_class_nameclutter_actor_get_typeclutter_actor_newshell_app_get_nameg_app_info_get_namemeta_window_get_titleg_dpgettextshell_app_get_descriptiong_app_info_get_descriptionshell_app_is_window_backedshell_app_update_window_actionsmeta_window_get_gtk_window_object_pathg_object_get_datagtk_action_muxer_insertmeta_window_get_gtk_unique_bus_nameg_dbus_action_group_getg_object_set_data_fullshell_app_can_open_new_windowg_action_group_has_actiong_desktop_app_info_has_keymeta_window_get_gtk_application_object_pathmeta_window_get_gtk_application_idg_desktop_app_info_get_booleanshell_app_get_stateshell_app_get_windowsg_slist_sort_with_datashell_app_activate_windowg_slist_findmeta_display_get_last_user_timemeta_display_xserver_time_is_beforeg_slist_copyg_slist_reverseg_slist_freemeta_window_foreach_transientmeta_display_sort_windows_by_stackingmeta_window_get_window_typemeta_workspace_activate_with_focusmeta_window_set_demands_attentionmeta_window_raisemeta_window_activateshell_app_get_n_windowsg_slist_lengthshell_app_is_on_workspacemeta_workspace_indexshell_app_compare_shell_app_new_shell_app_set_app_infog_utf8_collate_keyg_value_get_objectshell_app_get_busyg_value_set_enumg_value_set_stringg_value_set_boolean_shell_app_remove_windowg_signal_handlers_disconnect_matchedg_slist_removeg_signal_handler_disconnectg_cancellable_cancelg_slice_free1shell_app_get_pidsmeta_window_get_pid_shell_app_handle_startup_sequencemeta_startup_sequence_get_completedmeta_startup_sequence_get_workspacemeta_display_get_x11_displaymeta_startup_sequence_get_timestampmeta_x11_display_focus_the_no_focus_windowshell_app_request_quitmeta_window_can_closeshell_global_get_current_timemeta_window_deleteg_action_group_get_action_parameter_typeg_action_group_activate_actionshell_app_launchshell_global_create_app_launch_contextg_desktop_app_info_launch_uris_as_managerg_app_launch_context_setenvshell_app_activate_fullg_dgettextg_strdup_printfshell_global_notify_errorg_clear_errorshell_app_activateshell_app_launch_actiong_desktop_app_info_launch_actionshell_app_open_new_windowg_desktop_app_info_list_actionsg_strv_containsshell_app_get_app_infoshell_app_update_app_actionsg_strdup_shell_app_add_windowg_signal_connect_objectg_slist_nth_datag_cancellable_newmeta_window_change_workspace_by_indexg_slice_alloc0g_bus_get_syncgtk_action_muxer_new_shell_app_new_for_windowmeta_window_get_stable_sequenceshell_app_compare_by_namestrcmpshell_app_cache_get_defaultshell_app_cache_get_infog_app_info_should_showg_desktop_app_info_get_filenameg_app_info_get_executableg_app_info_get_commandlineg_app_info_get_display_nameg_icon_equalst_texture_cache_rescan_icon_themeg_timeout_addg_hash_table_remove_allshell_app_cache_get_allg_desktop_app_info_get_startup_wm_classg_hash_table_lookupg_hash_table_insertg_hash_table_foreach_removeg_hash_table_new_fullg_str_equalg_str_hashshell_app_system_get_typeg_hash_table_destroyg_source_removeshell_app_system_lookup_appshell_app_system_lookup_heuristic_basenameg_strconcatshell_app_system_lookup_desktop_wmclassg_ascii_strdowng_strdelimitshell_app_system_lookup_startup_wmclassg_hash_table_removeg_warn_messageshell_app_system_get_runningg_hash_table_iter_initg_hash_table_iter_nextg_slist_sortshell_app_system_searchg_desktop_app_info_searchg_utf8_validateshell_app_system_get_installedg_timeout_add_secondsg_source_set_name_by_idg_get_current_timeg_file_readg_markup_parse_context_newg_markup_parse_context_parseg_input_stream_readg_markup_parse_context_freeg_input_stream_closeg_hash_table_iter_removeg_settings_get_booleanshell_window_tracker_get_defaultg_dbus_proxy_new_syncg_build_filenameg_file_new_for_pathg_settings_newg_data_output_stream_put_stringg_markup_escape_textg_ascii_strtodg_markup_error_quarkg_ascii_strtoullshell_app_usage_get_typeg_file_replaceg_output_stream_get_typeg_buffered_output_stream_newg_data_output_stream_newg_ascii_dtostrg_output_stream_close_asyncshell_app_usage_get_most_usedshell_app_usage_compareshell_app_usage_get_defaultgtk_widget_get_typegtk_container_get_typeshell_embedded_window_get_typegtk_window_get_typeclutter_actor_queue_relayoutg_type_class_peekclutter_actor_is_realizedgtk_widget_map_shell_embedded_window_set_actorclutter_actor_is_mappedgtk_widget_get_visible_shell_embedded_window_allocategtk_widget_get_realizedgtk_widget_size_allocategtk_widget_get_windowgdk_window_move_resize_shell_embedded_window_map_shell_embedded_window_unmapgtk_widget_unmapshell_embedded_window_newg_getenvg_file_testg_get_user_data_dirg_mkdir_with_parentsXDisplayNameg_get_user_runtime_dirg_strsplitgjs_context_get_typeg_strfreevg_file_equalg_file_hashg_param_spec_intmeta_display_get_typemeta_workspace_manager_get_typeshell_wm_get_typeg_settings_get_typest_focus_manager_get_typemeta_set_stage_input_regionclutter_stage_get_key_focusmeta_display_set_cursorclutter_stage_get_typemeta_settings_get_ui_scaling_factorg_file_get_typeg_file_delete_finishg_io_error_quarkg_error_matchesg_cancellable_get_typeg_task_newg_task_set_source_tagg_bytes_refg_bytes_unrefg_task_set_task_datag_task_run_in_thread__gcc_personality_v0_Unwind_Resumeg_file_get_childg_variant_get_datag_variant_refg_variant_get_sizeg_bytes_new_with_free_funcg_file_delete_asyncg_bytes_get_datag_file_replace_contentsg_task_return_errorg_task_return_booleang_file_get_pathg_mapped_file_newg_mapped_file_get_bytesg_variant_new_from_bytesg_mapped_file_unrefg_file_error_quarkmeta_stage_is_focusedclutter_stage_set_key_focusg_idle_add_fullg_task_get_typeg_task_propagate_booleancogl_flushshell_perf_log_eventshell_global_get_typeg_hash_table_unref_shell_global_initg_object_new_valist_shell_global_destroy_gjs_contextshell_global_set_stage_input_regiong_malloc_nXFixesCreateRegionXFixesDestroyRegionshell_global_get_window_actorsmeta_get_window_actorsmeta_window_actor_is_destroyedg_list_reverseshell_wm_newmeta_plugin_get_displaymeta_x11_display_get_xdisplaygdk_x11_lookup_xdisplaymeta_get_stage_for_displaymeta_is_wayland_compositorst_entry_set_cursor_funcclutter_threads_add_repaint_func_fullmeta_get_backendmeta_backend_get_settingsst_focus_manager_get_for_stageclutter_x11_get_stage_windowshell_global_begin_modalmeta_plugin_begin_modalshell_global_log_structuredsd_journal_sendv_with_locationg_signal_emit_by_nameshell_global_init_xdndmeta_get_overlay_windowgdk_x11_get_xatom_by_nameXChangePropertyshell_global_get_pointermeta_cursor_tracker_get_for_displaymeta_cursor_tracker_get_pointershell_global_get_settingsmeta_display_get_current_timeclutter_get_current_event_timemeta_display_get_current_time_roundtripmeta_focus_stage_windowmeta_display_focus_default_windowshell_global_end_modalmeta_plugin_end_modalshell_global_reexec_selfg_file_get_contentsg_ptr_array_newg_ptr_array_addopendirreaddir64strtoldirfdfcntl64closedirmeta_display_closeexecvpg_strerrorg_ptr_array_freegetrlimit64sysconfshell_global_sync_pointerclutter_device_manager_get_defaultclutter_device_manager_get_deviceclutter_event_putmeta_display_get_startup_notificationmeta_startup_notification_create_launchermeta_launch_context_set_timestampmeta_workspace_manager_get_workspace_by_indexmeta_launch_context_set_workspaceshell_global_begin_workshell_global_end_workshell_global_run_at_leisureg_slice_allocg_slist_appendshell_global_get_session_modemeta_display_get_sizeg_value_set_intmeta_get_window_group_for_displaymeta_get_top_window_group_for_displayshell_global_set_runtime_stateshell_global_get_runtime_stateshell_global_set_persistent_stateshell_global_get_persistent_stateclutter_paint_volume_set_from_allocationshell_glsl_quad_get_typeclutter_actor_get_paint_opacityclutter_actor_get_allocation_boxcogl_pipeline_set_color4ubcogl_get_draw_framebuffercogl_framebuffer_draw_rectanglecogl_object_unrefcogl_pipeline_copycogl_pipeline_set_layer_null_texturecogl_pipeline_newcogl_pipeline_set_blendshell_glsl_quad_add_glsl_snippetcogl_snippet_newcogl_pipeline_add_snippetcogl_snippet_set_replacecogl_pipeline_add_layer_snippetshell_glsl_quad_get_uniform_locationcogl_pipeline_get_uniform_locationshell_glsl_quad_set_uniform_floatcogl_pipeline_set_uniform_floatclutter_clone_get_typeclutter_clone_set_sourcemeta_window_get_xwindowgdk_x11_window_get_xidmeta_window_get_compositor_privateclutter_actor_set_opacityshell_util_set_hidden_from_pickcairo_region_creategdk_window_input_shape_combine_regioncairo_region_destroygdk_window_lowershell_gtk_embed_get_typeclutter_actor_get_positionclutter_actor_get_anchor_pointclutter_actor_get_parentgtk_widget_get_preferred_sizeshell_gtk_embed_newclutter_effect_get_typeclutter_offscreen_effect_get_typeshell_invert_lightness_effect_get_typeclutter_actor_meta_get_typeclutter_actor_meta_get_enabledclutter_feature_availableclutter_offscreen_effect_get_texturecogl_texture_get_widthcogl_texture_get_heightcogl_pipeline_set_layer_textureclutter_actor_meta_set_enabledclutter_actor_meta_get_actorshell_invert_lightness_effect_newclutter_text_get_typeg_cclosure_marshal_VOID__VOIDg_ascii_tableg_type_check_value_holdsg_mallocg_task_get_source_objectg_async_result_is_taggedg_task_propagate_intg_task_propagate_pointershell_keyring_prompt_get_typegcr_prompt_get_typeclutter_text_get_textshell_keyring_prompt_newshell_keyring_prompt_get_password_actorshell_keyring_prompt_get_confirm_actorshell_keyring_prompt_set_password_actorshell_secure_text_buffer_newclutter_text_set_buffershell_keyring_prompt_set_confirm_actorg_value_dup_stringshell_keyring_prompt_completeg_task_return_pointerg_task_return_intdcgettextgcr_prompt_set_warningshell_keyring_prompt_cancelgcr_prompt_closeg_mount_operation_get_typeshell_mount_operation_get_typeg_array_unrefg_array_refg_strdupvshell_mount_operation_newshell_mount_operation_get_show_processes_pidsshell_mount_operation_get_show_processes_choicesshell_mount_operation_get_show_processes_messagestrchrg_output_stream_write_allg_queue_push_tailmemcpyg_string_newg_string_appendg_string_freeg_string_insert_cshell_perf_log_get_typeshell_perf_log_set_enabledg_hash_table_newg_queue_newg_get_monotonic_timeshell_perf_log_event_ishell_perf_log_event_xshell_perf_log_event_sshell_perf_log_define_statisticshell_perf_log_update_statistic_ishell_perf_log_update_statistic_xshell_perf_log_add_statistics_callbackshell_perf_log_collect_statisticsshell_perf_log_replayg_value_set_int64shell_perf_log_dump_eventsg_string_append_printfshell_perf_log_dump_logg_propagate_errorpolkit_agent_listener_get_typeg_idle_addpolkit_unix_user_get_uidgetpwuid_rpolkit_unix_user_get_typeg_list_removeg_cancellable_disconnectpolkit_error_quarkg_task_return_new_errorg_list_foreachg_list_freeg_list_lengthshell_polkit_authentication_agent_get_typeg_list_copyg_cancellable_connectg_list_appendshell_polkit_authentication_agent_registergetpidpolkit_unix_session_new_for_process_syncpolkit_agent_listener_registerg_error_newshell_polkit_authentication_agent_newshell_polkit_authentication_agent_unregisterpolkit_agent_listener_unregistershell_polkit_authentication_agent_completeshell_screenshot_screenshot_areag_task_report_new_errormeta_disable_unredirect_for_displayclutter_actor_queue_redrawshell_screenshot_pick_colorclutter_stage_captureclutter_stage_get_capture_final_sizeshell_util_composite_capture_imagesg_date_time_new_now_localcairo_surface_destroycairo_surface_referencemeta_enable_unredirect_for_displaymeta_cursor_tracker_get_spritecairo_region_create_rectanglecairo_region_contains_pointmeta_cursor_tracker_get_hotcogl_texture_get_datacairo_image_surface_create_for_datacairo_surface_get_device_scalecairo_createcairo_set_source_surfacecairo_paintcairo_destroymeta_display_get_monitor_index_for_rectmeta_display_get_monitor_scalecairo_surface_set_device_scalemeta_display_get_n_monitorsmeta_display_get_monitor_geometrycairo_region_union_rectanglecairo_region_xorcairo_region_get_rectanglecairo_rectanglecairo_fillcairo_region_num_rectanglesmeta_display_get_focus_windowmeta_window_get_frame_rectmeta_window_actor_get_typemeta_window_actor_get_texturemeta_shaped_texture_get_typemeta_shaped_texture_get_imagemeta_window_frame_rect_to_client_rectmeta_window_get_client_typeclutter_actor_get_resource_scalest_settings_getshell_screenshot_screenshotshell_screenshot_screenshot_windowshell_screenshot_get_typeg_path_is_absoluteg_get_user_special_dirg_get_home_dirg_strrstrg_strndupg_file_createcairo_image_surface_get_heightcairo_image_surface_get_widthgdk_pixbuf_get_from_surfaceg_date_time_formatgdk_pixbuf_save_to_streamg_date_time_unrefshell_screenshot_screenshot_finishshell_screenshot_screenshot_area_finishshell_screenshot_screenshot_window_finishshell_screenshot_pick_color_finishcairo_image_surface_get_formatcairo_image_surface_get_datashell_screenshot_newclutter_text_buffer_get_typeshell_secure_text_buffer_get_typeg_utf8_offset_to_pointermemmoveclutter_text_buffer_emit_deleted_textclutter_text_buffer_emit_inserted_textg_utf8_find_prev_charg_utf8_strlengcr_secure_memory_reallocgcr_secure_memory_strfreest_widget_get_typest_widget_get_theme_nodeclutter_actor_set_allocationst_theme_node_get_content_boxclutter_actor_get_first_childclutter_actor_allocateclutter_actor_get_next_siblingst_theme_node_adjust_for_widthclutter_actor_get_preferred_heightst_theme_node_adjust_preferred_heightst_theme_node_adjust_for_heightclutter_actor_get_preferred_widthst_theme_node_adjust_preferred_widthst_widget_get_can_focusclutter_actor_containsclutter_actor_get_last_childst_widget_navigate_focusclutter_actor_grab_key_focusshell_stack_get_typeg_param_spec_uintshell_tray_icon_get_typena_tray_child_get_typegtk_bin_get_typegtk_bin_get_childna_tray_child_get_titlena_tray_child_get_wm_classgtk_socket_get_typegtk_socket_get_plug_windowgtk_widget_get_displaygdk_x11_display_error_trap_pushgdk_x11_get_xatom_by_name_for_displaygdk_x11_display_get_xdisplayXGetWindowPropertygdk_x11_display_error_trap_popXFreeg_value_set_uintshell_tray_icon_newshell_tray_icon_clickclutter_event_typegdk_window_get_displaygdk_window_get_screengdk_screen_get_root_windowgdk_window_get_originclutter_event_get_timegdk_window_get_widthgdk_window_get_heightXSendEventclutter_event_get_stateclutter_event_get_key_codegdk_x11_display_error_trap_pop_ignoredclutter_event_get_buttonna_tray_manager_newgtk_container_addgtk_widget_get_visualgtk_widget_set_visualgtk_widget_show_allg_object_ref_sinkgtk_widget_destroyclutter_color_get_typeg_param_spec_boxedst_theme_node_get_icon_colorsna_tray_manager_set_colorsna_tray_child_has_alphacairo_pattern_create_rgbgdk_window_set_background_patterncairo_pattern_destroyshell_tray_manager_get_typeg_value_set_boxedshell_tray_manager_newshell_tray_manager_manage_screenna_tray_manager_manage_screeng_signal_stop_emission_by_nameg_file_get_parentg_file_make_directory_with_parentsg_output_stream_closeshell_util_touch_file_asyncg_object_set_datashell_util_get_transformed_allocationclutter_actor_get_abs_allocation_verticesshell_util_format_dateg_date_time_new_from_timeval_localshell_util_get_week_startnl_langinfoshell_util_translate_time_stringnewlocaleuselocalefreelocaleshell_util_regex_escapeg_regex_escape_stringshell_write_string_to_streamshell_get_file_contents_utf8_syncshell_util_touch_file_finishshell_util_wifexitedshell_util_create_pixbuf_from_datagdk_pixbuf_new_from_datashell_util_get_content_for_window_actorclutter_canvas_newclutter_canvas_get_typeclutter_canvas_set_sizeclutter_content_invalidatecairo_image_surface_createcairo_savecairo_translatecairo_restoreshell_util_check_cloexec_fdsshell_util_has_x11_display_extensionXQueryExtensionshell_util_get_translated_folder_nameshell_app_cache_translate_foldermeta_startup_sequence_get_application_idg_path_get_basenameshell_window_tracker_get_typemeta_workspace_manager_get_workspacesmeta_startup_sequence_get_typeshell_window_tracker_get_window_appmeta_window_get_transient_forshell_window_tracker_get_app_from_pidshell_window_tracker_get_startup_sequencesmeta_startup_notification_get_sequencesmeta_window_is_remotemeta_window_get_sandboxed_app_idmeta_window_get_wm_class_instancemeta_window_get_startup_idmeta_startup_sequence_get_idmeta_window_get_groupmeta_group_list_windowsmeta_window_get_wm_classg_direct_equalg_direct_hashmeta_workspace_list_windowsmeta_rectangle_get_typemeta_size_change_get_typemeta_window_get_typemeta_key_binding_get_typemeta_close_dialog_get_typemeta_inhibit_shortcuts_dialog_get_typeshell_wm_completed_switch_workspacemeta_plugin_switch_workspace_completedshell_wm_completed_minimizemeta_plugin_minimize_completedshell_wm_completed_unminimizemeta_plugin_unminimize_completedshell_wm_completed_size_changemeta_plugin_size_change_completedshell_wm_completed_mapmeta_plugin_map_completedshell_wm_completed_destroymeta_plugin_destroy_completedshell_wm_complete_display_changemeta_plugin_complete_display_changeg_variant_dict_unrefnm_secret_agent_old_get_typenm_connection_get_typenm_setting_connection_get_typenm_connection_get_settingnm_setting_connection_get_uuidsecret_password_clearsecret_password_clear_finishnm_secret_agent_error_quarknm_secret_agent_old_delete_secretsnm_setting_get_secret_flagssecret_service_search_finishsecret_item_get_secretsecret_item_get_attributessecret_value_unrefnm_connection_update_secretsg_variant_dict_newsecret_value_getg_variant_new_stringnm_connection_for_each_setting_valuenm_setting_get_namenm_setting_connection_get_idsecret_attributes_buildsecret_password_storevnm_setting_vpn_get_typenm_setting_vpn_foreach_secretnm_setting_vpn_get_service_typenm_connection_get_idshell_network_agent_get_typeg_hash_table_replacenm_setting_connection_get_connection_typenm_connection_get_setting_by_namenm_setting_enumerate_valuesnm_connection_get_uuidsecret_service_searchnm_setting_wireless_get_typenm_setting_wireless_security_get_typenm_setting_802_1x_get_typenm_setting_wired_get_typenm_setting_pppoe_get_typeshell_network_agent_set_passwordg_variant_dict_insertshell_network_agent_respondg_variant_dict_endnm_simple_connection_new_clonenm_secret_agent_old_save_secretsfopen64__isoc99_fscanffgetsfeoffclosegst_initshell_recorder_src_registergst_element_get_clockgst_element_get_base_timegst_clock_get_timegst_object_unrefgst_util_uint64_scale_intcairo_image_surface_get_stridegst_buffer_newgst_memory_new_wrappedgst_buffer_insert_memoryshell_recorder_src_get_typeshell_recorder_src_add_buffergst_mini_object_unrefgst_buffer_mapgst_buffer_unmap_gst_fraction_typegst_caps_new_simpleshell_recorder_get_typeshell_recorder_newshell_recorder_set_draw_cursorshell_recorder_set_areashell_recorder_recordg_string_append_lengst_parse_launch_fullgst_bin_get_typegst_bin_find_unlinked_padgst_element_factory_makegst_bin_addgst_element_link_manygst_element_get_static_padgst_pad_linkg_printerrgst_element_set_stategst_pipeline_get_typegst_pipeline_get_busgst_bus_add_watchclutter_threads_add_repaint_funcshell_recorder_close_nowgst_event_new_eosgst_element_send_eventshell_recorder_closeclutter_threads_remove_repaint_funcshell_recorder_set_framerateshell_recorder_set_pipelineshell_recorder_set_file_templategtk_recent_manager_get_defaultg_file_get_urigtk_recent_manager_add_itemgst_message_parse_errorshell_recorder_is_recordingg_dir_openg_dir_read_nameg_hash_table_containsg_key_file_newg_key_file_load_from_fileg_key_file_unrefg_key_file_get_locale_stringg_dir_closeg_get_system_data_dirsg_ptr_array_unrefshell_app_cache_get_typeg_app_info_get_allg_file_monitor_directoryg_file_monitor_set_rate_limitg_ptr_array_new_with_free_funcg_app_info_monitor_getg_object_add_weak_pointergst_base_src_get_typegst_base_src_set_formatgst_base_src_set_liveg_cond_initgst_element_get_typegst_push_src_get_type_gst_caps_typegst_static_pad_template_getgst_element_class_add_pad_templategst_element_class_set_metadatag_cond_waitg_queue_pop_headgst_buffer_get_sizeg_queue_foreachg_queue_clearg_cond_signalgst_base_src_set_capsgst_value_set_capsgst_value_get_capsgst_mini_object_copyg_queue_free_fullg_cond_cleargst_element_registergst_mini_object_refshell_recorder_src_closegst_plugin_register_staticg_string_append_unicharcairo_pattern_create_rgbagtk_widget_set_app_paintablegtk_widget_set_double_bufferedgdk_window_get_parentgdk_window_get_visualna_tray_child_newgdk_screen_get_typegdk_screen_get_displayXGetWindowAttributesgdk_x11_screen_lookup_visualgdk_visual_get_red_pixel_detailsgdk_visual_get_green_pixel_detailsgdk_visual_get_blue_pixel_detailsgdk_visual_get_depthcairo_get_group_targetgdk_cairo_get_clip_rectanglecairo_surface_flushXClearAreacairo_surface_mark_dirty_rectanglecairo_set_source_rgbacairo_set_operatorna_tray_child_force_redrawgtk_widget_get_mappedgtk_widget_get_allocationgdk_window_invalidate_rectXGetClassHintgtk_orientation_get_typegtk_invisible_get_typegdk_window_get_typegdk_selection_owner_get_for_displaygdk_window_remove_filtergdk_x11_get_server_timegdk_selection_owner_set_for_displayg_list_remove_linkg_list_free_1gtk_widget_get_toplevelgtk_socket_add_idgtk_widget_showna_tray_manager_get_typegdk_screen_get_defaultgdk_x11_screen_get_xscreengtk_invisible_new_for_screengtk_widget_realizegtk_widget_add_eventsgdk_x11_get_default_screengdk_atom_interngdk_screen_get_rgba_visualgdk_x11_visual_get_xvisualXVisualIDFromVisualgdk_x11_atom_to_xatom_for_displaygdk_window_add_filtergdk_screen_get_system_visualna_tray_manager_check_runningXGetSelectionOwnerna_tray_manager_set_orientationg_value_get_enumclutter_color_equalna_tray_manager_get_orientationlibgnome-shell-menu.solibst-1.0.solibgio-2.0.so.0libgobject-2.0.so.0libglib-2.0.so.0libgtk-3.so.0libgdk-3.so.0libcairo.so.2libgdk_pixbuf-2.0.so.0libgjs.so.0libmutter-clutter-4.so.0libmutter-cogl-4.so.0libX11.so.6libGLESv2.so.2libpolkit-agent-1.so.0libpolkit-gobject-1.so.0libgcr-base-3.so.1libsystemd.so.0libnm.so.0libsecret-1.so.0libgstreamer-1.0.so.0libgstbase-1.0.so.0libmutter-4.so.0libXfixes.so.3libgcc_s.so.1libc.so.6_edata__bss_startlibgnome-shell.soLIBSYSTEMD_209GCC_3.0GCC_3.3.1libnm_1_0_0GLIBC_2.7GLIBC_2.28GLIBC_2.4GLIBC_2.14GLIBC_2.3GLIBC_2.2.5/usr/lib64/mutter-4:/usr/lib64/gnome-shell	
o ��b�o�o0P&y
�oa_&	�oo p�U�o�oii
�o���	�oii
p���pii
pui	#pbEp�bE��bE0�bE�� bE bEHbEɃPbE�`bE�hbE�xbE��bE���bE��bE(��bE0��bE/��bE@��bEz��bEX��bE\�cE��cEt� cE��(cE��hcE��pcE���cE���cE˄�cEԄ�cE��cE��cE��cE��cE)��cE0�dEK�dET�dEt�(dE؆0dEr�@dE��HdE��XdE�`dE��pdE8�xdE���dE���dE���dEƅ�dE܅�dE���dE@��dE��HeE��PeE�eE`eE�eEpeE���eE�eE�eE���eE���eEʏ�eE�hE�eE�gE�eEfEfE�fE�fEfE@fE(fEt�@fE`fEhfEӈpfE߈�fE@gE�fEgE�fE�fE�fE��fE;�gE�gE��HgEH�PgE���gE���gE�gE�gEh��gE`hE�gE hE�gE�gE�gE��gE;�(hE��0hE?�hhE�phE���hE��hE�hE�hEX��hEiEiE�iE;�@iE�HiE�PiE��XiE��`iE���iE��iE��iE��iE ��iE���iE&��iE��iEQ��Ep�@�E�;��E���E���E�E�E@ �E((�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�E�x�E3��EI��E`��Eq��E���E���E���E���E���E{ȏEЏE4؏EM�ET�E�h�E}�oE�oE�oE�oE�oE�oE�oE�oE	�oE
pEpE�pEpEY pE�(pE
0pE8pE@pEHpEPpEXpE`pE�hpEppExpE�pE�pE�pE�pE�pE�pE�pE�pE�pE�pE �pE!�pE"�pE#�pE$�pEf�pE%qE&qE'qEIqE) qE*(qE+0qEc8qE,@qE�HqE-PqE.XqE/`qE0hqE1pqE2xqE3�qE4�qE5�qE6�qE7�qE8�qE9�qE:�qE;�qE<�qE=�qE>�qE��qE?�qE@�qEA�qEBrECrEDrEErEF rEG(rEH0rEI8rEJ@rEKHrELPrEMXrEN`rE�hrEOprEPxrEQ�rER�rES�rE��rET�rEU�rEV�rEW�rEJ�rEX�rEY�rEZ�rE[�rE\�rE��rE��rE]sE^sE_sE�sE` sEa(sEb0sE�8sEc@sEdHsEePsEfXsEg`sEhhsEipsEjxsEk�sEl�sEm�sEn�sEo�sEp�sEq�sEr�sEs�sEt�sEu�sEv�sE��sEw�sEx�sEy�sEztE{tE|tE~tE tE�(tE�0tE�8tE�@tE�HtE�PtE�XtE�`tE�htE�ptE�xtE��tE��tEL�tE��tE��tES�tEv�tE�tE��tE��tE��tE��tE��tE��tE��tE��tE�uE�uE�uE�uE� uE2(uE�0uE�8uE�@uE�HuE�PuE�XuE�`uE�huE�puE�xuE��uE��uE��uE��uE��uE��uE��uE��uE��uE��uE��uE"�uE��uE��uE��uE��uE�vE�vE�vE�vE� vE�(vE�0vE�8vE�@vE�HvE�PvE�XvE�`vE�hvE�pvE�xvE��vE��vE��vE��vE��vE��vE��vE��vE��vE��vEO�vE��vE��vE��vE��vE��vE�wE�wE�wE�wE� wE�(wE�0wE�8wE�@wE�HwE�PwE�XwE�`wE�hwE�pwE�xwEl�wE��wE��wE��wE��wE��wE��wE��wE��wE��wE��wE��wE��wE��wE��wE��wE�xE�xE�xEzxE� xE�(xE�0xE�8xE@xE�HxE�PxE�XxE�`xEWhxE�pxE�xxE��xE��xE��xE��xE6�xE��xE��xEA�xE��xE�xE�xE�xE�xE�xE�xE�xEyEyE	yE
yE yE(yE
0yE8yE@yEHyEPyEXyEb`yEhyEpyExyE�yE�yE�yE�yE�yE�yE�yE�yE�yE�yE��yE �yE!�yE"�yE#�yE$�yE%zE&zE'zE�zE( zE)(zE*0zE+8zE,@zE-HzE.PzE/XzE0`zE1hzE2pzE�xzE3�zE4�zE5�zE6�zE7�zE8�zE9�zE:�zE;�zE<�zEy�zE=�zEu�zE>�zE?�zE@�zEA{EB{EC{ED{EE {EF({EG0{EH8{EI@{EJH{E�P{EKX{E�`{ELh{EMp{ENx{EO�{EP�{EQ�{ER�{ES�{ET�{EU�{EV�{EW�{EX�{EY�{EZ�{E[�{E\�{E]�{E^�{E_|E`|Ea|Eb|Ec |Ed(|Ee0|Ef8|Eg@|EhH|EiP|EjX|Ek`|Elh|Emp|Enx|Eo�|EX�|Ep�|Eq�|Er�|Es�|Et�|Eu�|Ev�|Ew�|Ex�|Ey�|Ez�|E�|E��|E{�|E|}E}}E~}E}E� }E�(}ET0}E�8}E�@}E�H}E�P}E�X}E�`}E�h}E�p}E�x}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E��}E�~E�~E�~E�~E� ~E�(~E�0~E�8~E�@~E�H~E�P~E�X~E�`~E�h~E�p~E�x~E��~E��~E��~E��~E��~E��~E��~E��~E��~E��~E��~E��~E��~E��~EF�~E*�~E�E�E�E�E� E�(E�0E�8E�@E�HE�PE�XE�`E�hE pE�xE��E��EH�E��E��E��E��E�E��E��E��E��E��E��E��E��E��E��E��E��E� �E�(�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�E�x�E���E���E���E���E���E���E���E+��E���E�ȀE�ЀE�؀E[�E��E��E���E��E��E��E��E� �E�(�E�0�E�8�E�@�E:H�E�P�E�X�E�`�E�h�E�p�E�x�E���E���E��E��E��E��E��E��E��EȁEЁE؁E	�E
�E�E��E
�E�E�E��E �E(�E0�E8�E@�EH�E�P�EX�E`�Eh�Ep�Ex�E��E��E��E��E��E��E ��E!��E"��E#ȂE$ЂE%؂E&�E'�E(�E)��E*�E+�E\�E,�E- �E.(�E/0�E08�E�@�E1H�E2P�E3X�E4`�E5h�E6p�E7x�E8��E9��E:��E;��E<��E=��E>��E?��E@��EAȃEBЃEC؃ED�EE�EF�EG��EH�Er�Ej�EJ�EK �EL(�EM0�EN8�EO@�EPH�EQP�ERX�ES`�ETh�EUp�EVx�EW��EX��EY��EZ��E[��E���E\��E]��E^��E_ȄE�ЄEa؄EB�Eb�Ec�Ed��Ee�E��E��Ef�Eg �Eh(�Ei0�Ej8�Ek@�E�H�ElP�EmX�En`�Eoh�Epp�Erx�Es��Et��Eu��Ev��Ew��Ex��E���Ey��E���EzȅE{ЅE|؅E}�E~�E�E���E��Ez�E��E��E� �E�(�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�E�x�E���E���E���E���E���E���E���E���E���E�ȆE�ІE�؆E��E��E��E���E��E��E��E��E� �E�(�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�E�x�E���E���E���E���E���E���E���E���E���E�ȇEtЇE�؇E��E%�E��E���E��E��E��E��E �E;(�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�E�x�E���E���E���E���E���E���E���E$��E���E�ȈE�ЈE�؈E��E��E��E���E��E��E��E��E� �E�(�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�Eox�E���E���E���E���E���E���E���E���E���E�ȉE�ЉE�؉E��E��E��E���E��E��E��E��E� �Ew(�E�0�E�8�E�@�E�H�E�P�E�X�E�`�E�h�E�p�Ex�E��E��E��E��E��E��E���E��E-��EȊE	ЊE
؊E�E��E��E
��E�E�E�E�E �E(�E.0�E8�E@�E�H�EP�EX�E`�Eh�Ep�Ex�E��E��E���E��E��E8��E ��E!��E"��E#ȋE$ЋE%؋E&�E'�E(�E)��E*�E+�E,�E-�E. �E/(�E00�E18�E2@�EH�E3P�E5X�E6`�E�h�E�p�E7x�E���E8��E9��E:��E;��E<��E=��E>��E��ERȌE?ЌE@،EA�EB�EC�ED��EE�EF�E��EG�EH �EI(�EJ0�EK8�EL@�EH�ENP�EOX�EP`�EQh�ERp�ESx�EU��EV��E���EW��EX��EY��Ex��E���EZ��EdȍE[ЍE؍E\�E]�E^�E&��E_�E`�Ea�Eb�Ec �Ed(�Ee0�Ef8�Eg@�E�H�EhP�EiX�Ej`�Ekh�Elp�E}x�Ea��Em��En��E���E{��Eo��E]��E~��Ep��EqȎE�ЎEr؎Es�Et�Eu�Ev��Ew�Ex��H��H��3DH��t��H����5�D�%�D��h�������h��������h�������h�������h�������h�������h�������h��q������h��a������h	��Q������h
��A������h��1������h��!������h
��������h��������h������h�������h��������h�������h�������h�������h�������h�������h��q������h��a������h��Q������h��A������h��1������h��!������h��������h��������h������h �������h!��������h"�������h#�������h$�������h%�������h&�������h'��q������h(��a������h)��Q������h*��A������h+��1������h,��!������h-��������h.��������h/������h0�������h1��������h2�������h3�������h4�������h5�������h6�������h7��q������h8��a������h9��Q������h:��A������h;��1������h<��!������h=��������h>��������h?������h@�������hA��������hB�������hC�������hD�������hE�������hF�������hG��q������hH��a������hI��Q������hJ��A������hK��1������hL��!������hM��������hN��������hO������hP�������hQ��������hR�������hS�������hT�������hU�������hV�������hW��q������hX��a������hY��Q������hZ��A������h[��1������h\��!������h]��������h^��������h_������h`�������ha��������hb�������hc�������hd�������he�������hf�������hg��q������hh��a������hi��Q������hj��A������hk��1������hl��!������hm��������hn��������ho������hp�������hq��������hr�������hs�������ht�������hu�������hv�������hw��q������hx��a������hy��Q������hz��A������h{��1������h|��!������h}��������h~��������h������h��������h���������h��������h��������h��������h��������h��������h���q������h���a������h���Q������h���A������h���1������h���!������h���������h���������h�������h��������h���������h��������h��������h��������h��������h��������h���q������h���a������h���Q������h���A������h���1������h���!������h���������h���������h�������h��������h���������h��������h��������h��������h��������h��������h���q������h���a������h���Q������h���A������h���1������h���!������h���������h���������h�������h�������h�������h������h������h������h������h������h���q���h���a���h���Q���h���A���h���1���h���!���h������h������h�������h�������h�������h������h������h������h������h������h���q���h���a���h���Q���h���A���h���1���h���!���h������h������h�������h�������h�������h������h������h������h������h������h���q���h���a���h���Q���h���A���h���1���h���!���h������h������h�������h�������h�������h������h������h������h������h������h���q���h���a���h���Q���h���A���h���1���h���!���h������h������h�������h�������h�������h������h������h������h������h������h���q���h���a���h���Q���h���A���h���1���h���!���h������h������h��������h�������h�������h������h������h������h������h������h��q����h��a����h	��Q����h
��A����h��1����h��!����h
������h������h�������h�������h�������h������h������h������h������h������h��q����h��a����h��Q����h��A����h��1����h��!����h������h������h�������h �������h!�������h"������h#������h$������h%������h&������h'��q����h(��a����h)��Q����h*��A����h+��1����h,��!����h-������h.������h/�������h0�������h1�������h2������h3������h4������h5������h6������h7��q����h8��a����h9��Q����h:��A����h;��1����h<��!����h=������h>������h?�������h@�������hA�������hB������hC������hD������hE������hF������hG��q����hH��a����hI��Q����hJ��A����hK��1����hL��!����hM������hN������hO�������hP�������hQ�������hR������hS������hT������hU������hV������hW��q����hX��a����hY��Q����hZ��A����h[��1����h\��!����h]������h^������h_�������h`�������ha�������hb������hc������hd������he������hf������hg��q����hh��a����hi��Q����hj��A����hk��1����hl��!����hm������hn������ho�������hp�������hq�������hr������hs������ht������hu������hv������hw��q����hx��a����hy��Q����hz��A����h{��1����h|��!����h}������h~������h�������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h���q����h���a����h���Q����h���A����h���1����h���!����h�������h�������h���������h��������h��������h�������h�������h�������h�������h�������h��q�����h��a�����h	��Q�����h
��A�����h��1�����h��!�����h
�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h��q�����h��a�����h��Q�����h��A�����h��1�����h��!�����h�������h�������h��������h ��������h!��������h"�������h#�������h$�������h%�������h&�������h'��q�����h(��a�����h)��Q�����h*��A�����h+��1�����h,��!�����h-�������h.�������h/��������h0��������h1��������h2�������h3�������h4�������h5�������h6�������h7��q�����h8��a�����h9��Q�����h:��A�����h;��1�����h<��!�����h=�������h>�������h?��������h@��������hA��������hB�������hC�������hD�������hE�������hF�������hG��q�����hH��a�����hI��Q�����hJ��A�����hK��1�����hL��!�����hM�������hN�������hO��������hP��������hQ��������hR�������hS�������hT�������hU�������hV�������hW��q�����hX��a�����hY��Q�����hZ��A�����h[��1�����h\��!�����h]�������h^�������h_��������h`��������ha��������hb�������hc�������hd�������he�������hf�������hg��q�����hh��a�����hi��Q�����hj��A�����hk��1�����hl��!�����hm�������hn�������ho��������hp��������hq��������hr�������hs�������ht�������hu�������hv�������hw��q�����hx��a�����hy��Q�����hz��A�����h{��1�����h|��!�����h}�������h~�������h��������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h��������h��������h�������h�������h�������h�������h�������h��q�����h��a�����h	��Q�����h
��A�����h��1�����h��!�����h
�������h�������h��������h��������h��������h�������h�������h�������h�������h�������h��q�����h��a�����h��Q�����h��A�����h��1�����h��!�����h�������h�������h��������h ��������h!��������h"�������h#�������h$�������h%�������h&�������h'��q�����h(��a�����h)��Q�����h*��A�����h+��1�����h,��!�����h-�������h.�������h/��������h0��������h1��������h2�������h3�������h4�������h5�������h6�������h7��q�����h8��a�����h9��Q�����h:��A�����h;��1�����h<��!�����h=�������h>�������h?��������h@��������hA��������hB�������hC�������hD�������hE�������hF�������hG��q�����hH��a�����hI��Q�����hJ��A�����hK��1�����hL��!�����hM�������hN�������hO��������hP��������hQ��������hR�������hS�������hT�������hU�������hV�������hW��q�����hX��a�����hY��Q�����hZ��A�����h[��1�����h\��!�����h]�������h^�������h_��������h`��������ha��������hb�������hc�������hd�������he�������hf�������hg��q�����hh��a�����hi��Q�����hj��A�����hk��1�����hl��!�����hm�������hn�������ho��������hp��������hq��������hr�������hs�������ht�������hu�������hv�������hw��q�����hx��a�����hy��Q�����hz��A�����h{��1�����h|��!�����h}�������h~�������h��������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h���������h���������h���������h��������h��������h��������h��������h��������h���q�����h���a�����h���Q�����h���A�����h���1�����h���!�����h��������h��������h�������h��������h���������h��������h��������h��������h��������h��������h���q������h���a������h���Q�������%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%�CD���%�CD���%�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%�CD���%�CD���%�CD���%�CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݿCD���%տCD���%ͿCD���%ſCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݾCD���%վCD���%;CD���%žCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݽCD���%սCD���%ͽCD���%ŽCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݼCD���%ռCD���%ͼCD���%żCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݻCD���%ջCD���%ͻCD���%ŻCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݺCD���%պCD���%ͺCD���%źCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݹCD���%չCD���%͹CD���%ŹCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݸCD���%ոCD���%͸CD���%ŸCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݷCD���%շCD���%ͷCD���%ŷCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݶCD���%նCD���%ͶCD���%ŶCD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%��CD���%}�CD���%u�CD���%m�CD���%e�CD���%]�CD���%U�CD���%M�CD���%E�CD���%=�CD���%5�CD���%-�CD���%%�CD���%�CD���%�CD���%
�CD���%�CD���%��CD���%��CD���%�CD���%�CD���%ݵCD���%յCD���%͵CD���%ŵCD���%��CD���%��CDH��tH�����H������M��tL���^�H���������H��M��tL����H�����E1���H��t�H���w���H��tH���h�H�����H��tH���S�H�����E1�L����L�����H�����H��t�H��������H������H����E1�L���t�H���\�����H����L���[���H�<$H��t���H���3������H��L���3�H������H����M��t�L��������H����E1�E1���E1���f.�@��H�=U�C�P���H�=E�C��H�=��CH��CH9�tH��CH��t	�����H�=ɵCH�5µCH)�H��H��H��?H�H�tH�ŴCH��t��fD�����=��Cu+UH�=�CH��tH�=ΆC�Y���d����]�C]������w������H�U�CH��tH�I�C��H��H�=5�C�����uH�%�CH���H�=�����H�5�CH���5�H�=��CH����H��CH���f.���H�ʹCH��tH���C��H��H�=��C�����uH���CH���H�=I��4��H�5
�CH����H�=v�CH���6�H�g�CH���f.���H�E�CH��tH�9�C��H��H�=%�C�����uH��CH���H�=צ���H�5ͅCH����H�=�CH����H�߳CH���f.���H���CH��tH���C��H��H�=��C�����uH���CH���H�=h��4��H�5�CH����H�=f�CH���6�H�W�CH���f.���H�=u�C� ����H��H�G(���H�5�C�@��H��H�G(���AVAUATUSH�����H��H�3E1�jTI�Ź�L�%��CP1�H�=Z�H�-˱CjjATI������H��0�t�H��H�3I��jTI��E1ɹ j@�H�='�P1�AUjjAT���H��8H�3I��jTE1ɹ�AVH�=�1�j@AUjjAT�\��H���H��@1�H��H�=/�A�����H��[]H��A\A]A^�������SH��H�����H��[���fD��ATUS��t#L�<�H�
E���1�H�5���B�f�H��H�=s�H�����H��H�����H�T�H�5T�H��H��H�=[�1��q��I���Y���H��H�����H�
7�CE1�H�-QH��A�����1�RH�5��L�����XH��Z[]A\�C���USH��H��dH�%(H�D$1�H��H�$��H��t&H����H�D$dH3%(uOH��[]��H�,$�}�L��H��L�E1�I���EH�KH�1��P1����XZH�<$�	�����b��f���USH��H��Hc=��CH�H�{ H�� ���H�k �C��H�E���H�S �H��H�H��[]�k�ff.�ATUSH�� H�dH�%(H�D$1�H;t(L�٨H�
b��l1�H�5ͦ�o��H�S�H��I��H��D��H�
��Hc�H�>��H���x��H��H���m��H��H	���H����H����H�t$dH34%(�H�� H��H��[]A\�X�������L�������8�����f�H�t$dH34%(��H�� []A\��+��L��H��� ��H9�������D���L��H�����H9������D����L���D$�
����D$H�D$H9D$�����r���f�H������H��H���
���H��H�������������E���D苾��L���聾��9������$���@��L�����9���������@���L������9��������@���H9�t>H���n��H����1�H��1�����1���������fDL����H��I����H��L��H	�t�H��t�M��t�H������L������9�u�H�;H��t�1�1��@�EH�<�H��H��H��t�I�4�����t�1������(�����USH����t!L���H�
c���1�H�5����H��H����H��H���*���H�5��H�����H���CuH��t*H��H�����H��H��[]�����H��H��赾��H��u�H��[]�f���H��C�@��USH��H��Hc̬CH�H�G ����H���X�H��H�����H��H��[H��]���ff.����������SH�.����=����[�f���UH��SH����H�O�C�A�C��uuH��P��H��H�FH�C0H�����H�C H���H�C��H��H�����H��H��H�
�	H����H���H��[]�]��DH�5��CH���i����w���@��SH����H���C���C����H�߾P�Q�H���H�
�H�P0H�cH��H�H H�
uH�PH�H@������H��H����H����H�5����H�3H�
�H���H���H���H���[�fDH�5�CH��虹���V���@��H���CH��tH���C��SH�=��C�S����uH���C[�fDH�=L����H��E1�E1�j�0H�ƿH�
$��������PH��XZH�����H��H�=;�C���H�/�C[�D��AWH��AVAUATUH��H�=V~CSH��H��dH�%(H��$�1����H���H��I��L�l$ �J���H�D$H��H��H�D$�_�I�����L��H��H�D$��H��L��1��:��H��L��I�^軷���1f��TH���s�L��H���(���L��H��H���8��L�����I��H��t I�D$H�(�@ ��u�H��L���-�����I�|$ H�t$詶��1�1�L����軽��H�|$t'H�D$L��H�@I�,���H��H�����H�CH9�u�L���d��H��$�dH3%(uH�ĸ[]A\A]A^A_��J��f.���SH�����H�;H�����H��[H�@(��ff.���S��P�O�����1�H�5�H��[1�����f���AV1�I��H�=�AUM��ATI��UH��S����H�����L��H�����AUH��I��ATH��A�����1�H�5Ξ�V�XZ[]A\A]A^�ff.���ATI��UH��SH���j�L��H��蟻��H��H��H����H��H��tH��H�5{�1����H���`��1�H��[]��A\���AU1�I��H�=8�ATI��UH��SH�����H�����L��H���(���H��H��1�ATI��A�����H��H�5������ZYH��H��tH��H�5�1��z��H������1�H����H��[]A\A]�ff.�@��AU1�I��H�=��ATM��UL��SH���k��H���S�L��H��舺���t$0I��H��ATA�����1�H��H�5v����H��[]A\A]�f.���������AU1�I��H�=-�ATM��UL��SH�����H�����L��H������H��H��1�ATI��A�����H��H�5�����ZYH��H��tH��H�5͜1��Z��H�����1�H����H��[]A\A]�ff.�@��AU1�I��H�=��ATM��UL��SH���K���H���3�L��H���h����t$0I��H��ATA�����1�H��H�5i����H��[]A\A]�f.���AUI��ATI��UH��SH��H�����L��H������H��H��H���x�H��H��tH��L��H�5�1��l��H������1�H����H��[]A\A]Ð��AU1�M��ATI��H�=��UL��SH���k���H���S�L��H��舸��H��H��1�t$8I��A�����H��H�5���#��ZYH��H��tH��L��H�5w�1�����H���-��1�H����H��[]A\A]�f.���SH�=�1�H���ڿ��H��[H�����ff.�����f.���SH�=��H��1���蘿��H��[H�����ff.����SH�� dH�%(H�D$1�H���CH��t+H���CH�L$dH3%(��H�� [�f.�H�=Y�C�����t�H�=x�����H����H��H��L�
n���jA�(��H��H�
�������H��XZH�����H�D$��CH���H�$H�D$�ؿ��H��H��H��誳��H��H�=��C�k���+����a�����U1�SH��H������H��H��葶��H���I�H��H���~���H�5�H���O���H��tH��H�����H�߉��E��H����[]�ff.����AWAVI��AUATL�%{�UH��SH��H��(dH�%(H�D$1�L�l$�T��H��H�����H�T$H�58�H��H��1��p���<fDH�t$H�=vC�g��H�|$H������H�} 1�1҉�諭��H����H�|$1�1�L��L��������u�H�|$�p���I�6H��tgM��1�L�-�uC@L�����I�<$I���d��H�} 1�1҉��E���M��tM�g(�PH���/���H��L������CM�$�H��I�4$H��u�H�D$dH3%(u2H��([]A\A]A^A_�f�H�[(H��P�ߴ��H��H�������-����z��f.���SH�����H��H��訴��H�x �_��H�=��C�P�~�H��[H�@0��@��AWI��AVI��AUI��ATA��UL��SL��H�����H��jH�
��RH�5��H�=��H�1�QL�
��H��AWV1�AVWH��1�AURH��ATL��$��t��H��X[]A\A]A^A_�D��ATI��UH��S���H���%���H��H���ʳ��H��L��H���,��H��H������H��t觼��[H��]H��A\阳���[1�]A\�f���AWI��AVI��AUI��ATA��UL��SL��H�����H��jH�
��H�5��H�=��RE��QL�
-�H�
}�H��AWVH��AVWH��1�AU�^��H��@H��t%H�����H��H��H��[]A\A]A^A_���@H��1�[]A\A]A^A_�ff.�@��AWI��AVA��AUI��ATA��UL��SL��H������H�/�jH�
�RH�5�H�=�H�q�QL�
��H��AWV1�AVWH��1�AURH��ATL��$����H��X[]A\A]A^A_�D���7������AWI��AVA��AUI��ATA��UL��SL��H���G��H���jH�
F�H�5P�H�=d�RE��QL�
��H�
�H��AWVH��AVWH��1�AU���H��@H��t%H��蝺��H��H��H��[]A\A]A^A_鄱��@H��1�[]A\A]A^A_�ff.�@��SH�� dH�%(H�D$1�H�8�CH��t+H�,�CH�L$dH3%(��H�� [�f.�H�=�C������t�H�=����H���\��H��H��L�
.�jA�(�0H��H�
7�蒪���(H��XZH���0��H�D$���CH�Z�H�$H�D$船��H��H��H���Z���H��H�=h�C����+���������USH��H���^��H��H���C���H��H�@ H�x �s��H�C H�8臿��H�{ ��H�� ���H����[]�ff.�@��AVAUM��ATUL��SH�� dH�%(H�D$1�L�t$P����H�|$XH���ï��f�H��H�=pCI��)$H�D$�@��H����H�p(I�<$H�����H����H��C0uUH��L���z���H�[(�PL���Y���H��H��H�����H���C���H�L$dH3%(umH�� []A\A]A^ÐL��H������L�њH�
Җ��1�H�5���O����3���I��L����H�
e�1��8��1�����l��ff.����AVM��AUATM��USH�� dH�%(H�D$1����H�|$PH���x���f�L��H�=�nCI��)$H�D$���H����H�p(I�}H������H��H����I��H�pL���1��H�k(�PL������L��H��H���r��H�{�)���L��H���.���L��H������H�L$dH3%(H��uZH�� []A\A]A^�L���H�
B���1�H�5]��������M��L����H�
�1�������!�����AWAVAUATUL��SH���L��$ H�L$ L�D$(L�|$dH�%(H��$�1��9��H��$(H������f�L��H�D$@I��)D$0�l��H����I��H��1��f��A�T$0�����H��H�H��H�$�t��I��輵��L��H��H�D$���L��L��M�n�M���H��L��H�����L��L���2��A�L$0�����L�l$PH��H�T$L��蛦��H�T$H�RH)�I�,�H���)��TH���C��L��H�����L��H��H�����L���и��I��H��t I�D$H��@ ��u�H��L�������I�|$(H�t$H�\$0�t����H�߉�����1�L��H�ى��v���H����������H�����H�<$t(H�$L��H�@I�,��H��H���t��H9�u�L�����H��$�dH3%(��H���[]A\A]A^A_�fDL���H�
z��`1�H�5Ս�w����[���L�L$ L�D$(H�
"�H�|$�ƺ1�����<���f����M�n0H��L������H�|$�b���H�����L��H���_����5����P�����AWAVAUATUSH��H��(dH�%(H��$1�L�l$�{��H��H���`���H��H�@ H�x ���H�=���t���L��H���y���H�=��]���H��$�H��H��H�T$�U���H�M L�yM����E1���M�M��tsH�M M�7A�F��H�4@H�H��I�vH���z���u�I�A��H�x���H��H�����H�5�L��H��I�H��H�P1����H���u��M�M��u�E���H�L$L��H�5�H�=�1�L�5�����H��觹��I�����H��H��I���1���H�����H�D$I��H��tE@L��H��I��
���H�����H��M��M��jH��H�
D�1�H������M�XZM��u�L�����H�5p�CH�|$����H�E H�5��H�x�
���H�} H�GH�� H�G����1�H��$dH3%(u,H��([]A\A]A^A_�fDL�����H�|$����������SH���C��H��H���(���H��H�@ H�x �X���H�C H�xH��t+�F��H�{ H�GH�� ����H��[�H����H�x [����fD��AUATUSH��H��dH�%(H��$�1����H��H��H��薧��H�=ڊH��跢��H��H��輷������H��H��I���i���H���A���L��H��I���S���H���;���H��L��E1�UH�
؉1�H��L�ʼn�x���ZYH��t.H��H��趱��H��H��1�H���H�5L��+��H������H���+���H��$�dH3%(uH�Ę[]A\A]��u���D��USH��H�����H��H��裦��H��H�@ H�x �Ӽ��H�] H�{tH�{tH��H�{ []�r���f��ˬ��1�H�CH�E H�x�h���H���P���H�U H�
�CH�5����H�zH������H�E H�5��H�x���H�E H�pH�x�-���H�E H�x耬��H�] H��H�{ []���ff.�f���AWAVA��AUI��ATI��UH��SH������H��H��貥��A��t$L��H�
����1�H�5I�����H��H�@ H�x 踻��H��萶��H�C L��H�0����t-H�{ H�� �P���H��H��[]A\A]A^A_�J���f.����H��H��� ���H������H��H�C L�8t	��eCuL��L�����L��H��轻���H�@L�5�eCH��t)H�L�5~eCL92u���H�L92t�H�@H��u�(���L�0L�s H���@I�~H�D$覻��H�T$I�7I�FL�rL���~��L��L�����H�C L�8�T������ATI��U��SH���K��H��H���0�����t+L�d�H�
��81�H�5Ȇ�j��f.�H��H�@ H�x �0���H�C L��H�8���H�{ []A\H�� �о����UH��SH������H��H��賣��H��H�@ H�8���H�C H�8�8���H�C H�5]��H�x���H�C H�xH��t	���H�C H�x����H�{ H�� ���H�=i�C�P�7��H��H�@0H��[]�����S�&���H���.��1�H��1���H��[H������fD��1��f���H�5gC�@���ff.�H��dH�%(H�D$1�����1�H��H�5(�H��1�����H�<$����H�$H�L$dH3%(uH����*���f.���SH�����H��[H���w������SH���s���H��[H��鷩�����H���S���H��H���������SH���3���H��[H��������ATI��U��SH������L���H��H��[]A\�ǹ�����AUE��ATA��U��SH��H������H��E��D���H��H��[]A\A]�E���D��H�����H��H��闸�����ATA��UH��SH���z���D��H��H��H��[]A\���D��H���S���H��H���g������SH���3���H��[H��������ATA��U��S������D����H��[]A\�y���f���SH�����H��[H���7������AUM��ATI��U��SH��H�����H��M��L���H��H��[]A\A]�5��D��SH�����H��[H���g������SH���c���H��[H��釟�����SH���C���H��[H��駧�����SH���#���H��[H��釰�����SH���c����C���-萭��H��H����H�����H��H�
����H���H�5r���H�K���H���H�
���H�=����H���H�5���H���H�J���H���H�
���H���H�5���H���H�=����H���H�b���H���H�$���H���H�
���H���H�5��H���H�=����H���H���H���H�~���H���H�
P���H��H��H��H��H�� [�H�5ɊCH���Y������@��H���CH��tH���C��SH�=��C������uH�x�C[�fDH�=ņ�IJ��H�����H��H��L�
>���jA�8�(H��H�
'����җ��ZH�=*�CYH�����H��C[����ATUSH��H�� dH�%(H�D$1��̘��H��H��豝��H�D$H��耭��H���x��H�C(H�����H��褹��H��茰��1҃����C ��	ЈC �О��H�
M�H�j�H�5(�H��賧���n��H�C0����H��H���*���H�{0H���α��H�{0襓��L�L$L�D$H�����H�
�H�5V�H��H���;�����t~H�D$dH3%(uiH�� []A\�fD�ã��H�=Y�H���T���H�=c�I���E���H�SH�sH���H������H���A��H�5I�H�����H�����������H�D$H���� 1�H�H1�����H�|$葘��H���i����迶��f.�D�������H�G�f�H�W(1�H��tMUSH��H�ZH��t01��f�H�[H��tH�;蟑��9�r�H�;蓑��H�[��H��u��H��[]����H������H��菳��H������H��H�����ff.�H�G(H��t7SH�XH��u�#H�[H��tH�;�ר����t�1�[���[Ð1��ff.�f�����H�5c�C1����ff.����S1Ҿ������x[�þ���=������t8����,������t��[�o����3����8t։�[�V��fD�����8u��@[�ff.�H��H� t%L�n�H�
���1�H�5k�����@H�G(H��tH�@H��tH�H���fDL�N�H�
‰��1�H�5&����ff.�@��UH��SH��H��H�>t�ř��H9tH���[]�@H�[H��H�;�Q���H�H���[]�f���AWAVAUATI��UH��SH��H���p���L�{L��I��I9�A���Z���H�SH9���tE��uiM9�t��t�H��[]A\A]A^A_�@H���Ц��L����Ʀ����t��t.��u��u�L�����H������H��)É�[]A\A]A^A_Ð������f���H�B(H��t+H��H�PH9:t�H�55�C1�H��1�������H��L��1���H�
o�H�5������G9�tY��u��t7SH���w���H��H�����H�߾P�k���H�5ł[H���K���H���H�5Z�1��s����ff.�@H�G(H��t/�t!�@��u
1��s�����f���fD��H�"�H�5�1�������SH�z(t<H�������uH�C(H�߃@[�f.�H�C(H�߃h[�o����L�H�
z���1�H�5~��������H���CH��tH���C��H��H�=��C������uH�u�CH���H�=u�贫��H��L�
����A�@j��H�ƿPH�
-�Ȑ��ZH�=0�CYH��藻��H� �CH���ff.���SH���S���H��C���C����H�߾P���H��H��H�C H��
H�CH��H�C(H�fH�C0荫��H��E1�E1�jH��1ɺjH�=��1�j�5���H�� ���C����A�E1�H�{�H��H�5�tH�=v��0���H�߾H������A�1�H�X�H�5�xH�=��莐��H�߾H���α��A��1�H�O�H�5*�H�=/�蜓��H�߾H��蜱���׵��A��H�B�H�5�H��H�=��t��H�߾H���d������H�H�H�5�A��H�=�H���<��H�߾[H���+���H�5i�CH������f���@��USH��H������H��H����H�x0H��臭��H�}8�~���H�='�C�P���H��H�@0H��[]��ff.���SL���é��H��H��蘔��H�P(H��t�J�5݀CH��1�1�[鈝���L��~H�
���1�H�5V~���ff.�@��UH��SH��H���K���H����H�H��tH;tH��H���*�����tvH��1��\���H��H��t3H�C(E1�E1�H��H��H��H�5�~H�h0�M���H��腏����uYH�C(H��tH�x8H��t
H�@8�ҷ��H��H��[]�ķ��@L�H~H�
���1�H�5v}�Ͽ���H�߾P�[���H�5.~H���<����f.���SH���S���H��t>H�H��tH;tH��H���6�����t"H�߾P����H�5�}[H�����DL��}H�
2���1�H�5�|�/���ff.�@��SH�_ H��u
H�G0[�@胨��H��H��蘒��[H��鿳��ff.�@��ATA��UH��SH��dH�%(H�D$1�H� ��螐��H���V���H��H���;���D��H���p����;���H��H��� ���H�5}H��葾�����H�} H������H���H���H������H��H�����H��H������H�L$dH3%(H����H��[]A\�f.��ۺ��H���c���H��苔��1�H�T$H�5l|H��1����D�d$H�}(tfH������H��tY�PH���\���H���Ԯ��D��H�K|H��H���ߝ��H�5,|H��H��轵�����H��H������H���;���D苸��f�E1�1��A*�H��H�ǸL��H�
��H�5�{�Z�f(�耫�����v���fD��SH�_ H��t"荦��H��H��袐��[H���	���f����H��tH���.���H��t	[���H�5�{H�=9�[�׮�����SH�_ H��t"����H��H���2���[H�����f�1�[�ff.����1�H� ��Ð��AUATUH��H��SH��H���ԥ��H��to�PH��I���Ϗ��H�5�zH����I��H���|H�E(H�xH��tGL��H�5�z����H��P苏��H��H�5z[H��]A\A]�b���f�H��[]A\A]�DL��zH�
�~��1�H�5Fy蟻����s���H��I��訟��H�U(H��H�z(L��蕮��L��H���
���H�߾PI�����H�
�yCL��H�5zH���A����(���ff.����AUATUSH��H�_(H����H�����H�{H��衎��H�5�yH��������u^L�e M��tZ�@���L��H��I���r���H�5�yH���C�����u?H�{ t(H�CH�H��虞��H��tH���<���H���������H��[]A\A]�H�} L������H�5qyH���U�����������ff.����G����USH��(dH�%(H�D$1�H�G(H��t
�@uH�@H�L$dH3%(uAH��([]�@H��H�<$���H�k(H��H�5��H�D$H�}�@���H�EH�C(�`���
���ff.�f���AWAVAUATA��UH��SH��H��HdH�%(H�D$81��`�����t+H�D$8dH3%(��H��H[]A\A]A^A_��H���Ș��H��H��uH��tH�H��H���ݐ��H��t���H��諤��I����H��H�$�w���L��I���̧��D��L����蟖�����H���O���H���7���H�D$H��t/I��f.�I�/H9�tH��� ���I9���M�M��u�H�|$�$���H��H�D$��H�T$ H�5�H��H�D$ H�D$H�D$(耚��H�t$L���ß��H��諓��H�|$I��H�D$�ɿ��H�D$M��t I�/H��蝣����tY��tTM�M��u�H�|$葿��L9,$t{D��H��L���-����m����H�������X���H����������H�|$�F���H��t�H���	���H��A�����D��L�����A�����HE��f�D��H���������軡��ff.���H�G(H��tH�x�:���f.�1��ff.�f���UH��SH��H�������tNH�C(H��t5H�XH��u
�*f�H�[H��tH�;�_���H9�u�H���[]�fDH��1�[]���{�t�H��蚢��9C��H����[]�f.����G;Ft��t~��f�ATUH��SH����H��A����A9�t��uR�[]A\�1��{u�H�S(H�E(H�zH�@tH��t%H����H�߉���)ʼn��H��u������ø�����@��SH��賞��H��1�[H��H�59t1��\��ff.����ATUSL�g H��L9�t!H��H��tH���L���H�k M��tL���k���H�{8H��t
H�C8�š��H�{ tH�����H�����H���7���H�C8[]A\�ff.���AVI��AUATI��UH��S����H��H���Ɉ����t\H�EH�8���H��I�$H�8���UL�
t1�PA��1�H�
�rA�t$H��u�S�H���H�� []A\A]A^�L��I�����[L��]H��A\A]A^麡��f.���H�W(1�H��t!H�z0H��tH���߃������H�����f��ff.�@��AUA��ATI��UH��SH��H������H��H���և��A����H�5uuD��Hc�H�>���H�p H����H��H��[]A\A]�A�����pH��H��[]A\A]�k���H����}��H��H��[H��]A\A]�k���H�@(H��tH�p�f�H���}��H��H��[��]A\A]�$���@H�H�8�U���H��I�$H�8�F���SL�
wr1�PA��1�H�
�pA�t$H�t�AU褉��H�� H��[]A\A]�D��ATUSH�G(H����H��H�xH��謊��H����H��E1�1�1�SL�
p�H��胎��E1�1�1�L�
e��H��H�$�d���E1�1�1�L�
f��H��H�$�E���XH��Z�[���L�c(H��I�|$���H��I�D$�-�����t)H�����H�k(H�}tV�5HrCH��1�[1�]A\��H�C(�h��fDL��oH�
�s�H1�H�5�o�����[]A\�H�C(��H���{���H�����U��u$L��pH�
>s��1�H�5bo軱�����U�V����uH��街��H�}0H��t
H�E0�[���H�}8H��t荈��H�}8H��t
H�E8�7���H�}H��t
H�E�!���H�}(H��t
H�E(����H�} H��t
H�E �e���H��@蘳�������UH��SH��辙��H��H��蓄��H�x H��H��t&H�@ 誨����H�BH��H�0葀��H�S(H��u�C��t!L��qH�
�s��1�H�57n萰��H�=ypC�P����H��H�@(H��[]����H��H��H���.���ff.���ATUS�Ï��H��tVH��E1��fDH�[H��t2H�;�{��L��Hc�H�����H��u�L��H���!���H�[I��H��u�L��[]A\�DE1�[]L��A\�ff.�f���ATUH��SH��H���j�����t6H�C(H��tH�x�tH��[]A\��DH��1�[]A\��f�H���X�����tH������C[]A\�@�+���H�����H���+����H��I���;�H���#���L�����{��H��衉���C[]A\�f���USH��H���ޱ��1҃�tH����[]�fD�â��H�S(H��H�z�s���H�5nH��H�������uMH�C(H�XH��t/H�+H���E�����t�l���H���Ĭ��H����Z���H�[H��u�H�����[]�H�5�mH��聎��H��u�1�H�5�mH��軜����J������AVAUATU��SH� H����A��A��M�������D��H���ɰ��H��E��uQH���x��H��H�{ H��I��H�T�AT1�jL�g��P�ܔ��H�� H���莥����[]A\A]A^�H�NyH�5�lH��芓�������H��tÉ�H�������D��AVAUA��ATUH��S��H��dH�%(H�D$1�����I�ą����E��t��t<������1�H��至��H�D$dH3%(��H��[]A\A]A^�fDI��1�D���M��H��H�$�#�����u�H���G���H�5	lH�=5tH��豩��H��H��1��T���L��H��H�$H��H�P�Ϋ��H���Ƙ��L���.����b���f�H��蘪�����)���E1�H�
7o�1�H�5�i�D���菖��ff.�@��1Ҿ����� �����AUA��ATI��UH��S��H���Ҩ��D���H��赮��H���M���I�|$ H�����H��H��H��肋��H��H��[]A\A]鐣����ATUSH�_ H����A��H������H��H���5��H���}���H�5jH���.�������H�](H��t,�H���H�{H���~��H��H��tH�5BjH���e�����u9[D��H��E1�]1�1�A\�{���[H��j]1�H�5�mA\��w���H�5�iH������H��u�H��1�H�5�i[]A\�O����[D��H��1�]H�5�iA\�0w����H�G ����AUATI��USH��H��H��贎��H��H�C(H��H�x �A�����u
H��[]A\A]�f�L���X���I��H��t�H��t�L�c(I�|$ H��tI�D$ ����L�c(H����L��H��I�D$ H�C(H�x(�;���H����H��H���}��H�S(H�56oH�zH������H��H��[]A\A]鯡��ff.�@��AUATUH��SH��H��H�G(H��t#H�x�Z���H��tH��[]A\A]�f.��PH���#}��H���;���H�{(�p�s���UH�C(�C����H��H��s��L�c(H��I�|$�]z��E1�H��H��I�D$H�����H�5�h�ܯ��E1�H��H��H���H�5�h���E1�H��H��H���H�5�h褯��H��H��蹁��L�c(I�|$0tMH��腊����uH�C(�@H���!��PH���D|��H���L}���5�hCH��1�H��1�[]A\A]�0���I�|$8u�I�|$ t�I�|$1��d���H���L���I��H��t��ό��H��I�D$8��r��H��I�T$ L��M�D$8I�|$(P�L�
�芭��XZ�F���1�H����������ˤ��H��胓��H�����H�{(I��t!L�bgH�
j�c1�H�5ge����@�6���E1�E1�L��H�C(H��H���H�5
g�譌��L�c(1�1��A�D$�r��I�D$(L�c(I�|$(t���I�D$���fDL�ihH�
�i�l1�H�5�d�/���ff.�@��UH��SH���Ώ��1�H��1��p��H��H��觇��H�=�f��1�藅��H��H��H�C0�h{��H��H��[]�ff.���H�v8H�8�ߚ��f.�D��AVH��AUATUSH���Y���1҅�t[��]A\A]A^��H���p��H�����H��H��腙���H��H��t�H���@z��I��踏��L��H��H����y��H��H��I���y��L��H�����H��A���֥��A9�t�[]��A\A]A^ÐH���8���L��H���-���H��H��������u�H���p��L��H���p��H��H���p�����u�H��蔨��L��H��艨��H��H���N�����u�H���}��L��H���}��H��H��蜙�����c���H��輩��L��H��豩��H��H���������=���H���f���L��H���[���H��H���P���������H������L��H�����H��H�����1҅����k���ff.���SH�_��H���
~���K$�Q��w��u�S$�[ÐH�C 1�[�@��AVAUI��ATUSH�^�C �C$��uH���	H�5������r���C I�]H�{�|���֒��H���~���H��H���|f�L�eL������I��茥��L��H���w��H����q��I��H��tBH�{H��蕊��H��tL��L���%�����u"L��詀��L��I��螀��H�{L��H���o���H�mH��u�I�E1�H�5���H�x� ����5�cC[L��]1�A\1�A]1�A^�D���@��AU1�1�ATUH��SH��Hc�cCL�%�aCH�L��H�_1����L�--aCL��1�L�%QaCH�L��L�����H�DaCL��L��H�CH���r��H�C虑��H��E1�E1�H�5�fH��H�_���H������H��H��H��[]A\A]�B���f���H��bCH��tH��bC��SH�=�bC������uH��bC[�fDH�=�e�Ċ��H��L�
����A� j��H�ƿPH�
=��o���(H��XZH���v���H��H�=TbC�VbC葚��H�BbC[����SH���S���H�4bC�&bC����H��H�C0辊��H��薙��SE1�E1�jH��1ɺjH�=Ke1�j�a���H�� ��aC�b���H��E1�E1�jH��1ɺjH�=&e1�j�*���H�� ��aC[�fDH�5�aCH����o���f���@��UH��SH�����H��H����t��H�XH�;����H�{���H�{���H�{H�5r_C�%����{ ��u&H�=/aC�P蝤��H��H�@0H��[]��fD�C �����f.���H��`CH��t��H���_���1�H��1��3j��H��`CH������ATUH��SL�gI�|$����H��H��tH��[]A\�����H��H��萓��H��H��t�H��蠋��H��H���ej��I�|$H��H����}��H��[]A\�ff.�f���AVI��AUI��ATUS�ɇ��H��H��tH��[]A\A]A^�DL�%8CH�wc�
I��I�$H��t�1�H��L��1�贔��L��H��H���v���H��H�����H��t�H��[]A\A]A^�f.���AUATUSH��H����H��1�H��1�H�5cH���P���H��H��I����|��L��I��臋��M��tH��L��[]A\A]�@H��H���������-H�5�{H��H����p��1�H��1�H�5�b���H��H��I���i|��H��I������L������H��L��[]A\A]��H��E1�[L��]A\A]�ff.�@��H��t/SH�GH��H�x����H��tH��H��[�5���D1�[�@1��D��UH��H��SH��H���(�����t"��t?��uRH����h��H�M1�H��H�9�{���5;^CH��H��H��[1�1�]�z��fDH�EH��H�8聓�����E1�H�
�a��1�H�5ua�[����f���AUATUS1�H��HdH�%(H�D$81�H�GH�l$L�l$H��I��H�0��}���f�H�4$H���n��H��L��L��H��������u�H�5�[CH���v��H�L$8dH3%(uH��H[]A\A]�耇����AUATUH�����SH���y��H�I��I��H��u �3fD1�H���&�����uH��H��H�;H��u�I��I�$H��u�H��L��[]A\A]�D��H���3���H��H���ז�����ATI��USH���f��H�{PH������L��H���f��H�{PH�����[��\E]A\�,���ATUH��H��S�af��H�}PH��I���‚��H��H��t
H��[]A\�f������L��H����x��H�}PH��H���y��H��[]A\�@ATUH��SH��H��@dH�%(H�D$81��}���H�X+]@��v[H���%I�$f���)������*��Xf/b�wJ�E4��u%H�5T	�,H���W���H�5�_�E4���v���H�D$8dH3%(uQH��@[]A\�DH�uPH�\$L�d$H���{���H�D$��a�Y�1�L��H��讂����u��s����P�����AUI��ATUSH��H��(H�jHdH�%(H�D$1�I��H��t%L���Ho��H�$H��H������H�{HH��t�{���1�1�H�SHL��H�5�_蔉��L���o��H�$H�C@H�D$dH3%(uH��([]A\A]�贄��@AVI��1�AUATUSH��PH�dH�%(H��$H1�H��H�$H���t��H�<$H��tY�u3�i��H��$HdH3%(�H��P[]A\A]A^��H�O1�H�#^�1��7p��H�<$뮐1�L��1�I��H�=�XCH�l$@����I���H��H��H��L���z����t1�I�غH��L���F���H���L��H�\$H�l$���1�1�L���S~��L������H����m��H�D$I�vPH��L������{y��1�H��H��軀����t'H�D$��_f/v�L;`~�H����z����@H�$H������H�H����%���DUH�5h^SH��H��H�(�c���Ņ�u1�C<��tH�{HH��t�l����{4H�CH��u5�k<H��[]��S<��u���p��H��1�H���g����k<H��[]�D�S����C4�f.���ATUSH��H��dH�%(H�D$1����H�%VCH�5�UCH�=VCI��H���Lt��H�CP�sp��E1�E1�H��H���H�5]H����|��蟞��E1�E1�H��H��H�5)[H����|��1�1����b��jL�
�\1�jL��1�L�	\H��H������ZE1�YH�C E1�H��H��H�eH�5�\�y|��H���!���1�1�H��H�C8H�5�\L���3���H�<$1�1�H�5�\��n��H�<$H���S���H���[���H��H�C�?���H�����H�=m\�c��E1�E1�H��H�C(H�7H�5g\H����{��H����H�D$dH3%(u	H��[]A\���f���H������@��SH��賒��H��VC��VC��u"H�߾P���H��H�P0[��H�5�VCH���d����ff.�@AV1�I��AUATI��UH��H�=�[S�t��1�H��L��H��H���^s��H��A���3���E��u[1�]A\A]A^�H�����L���1���1�H��L��H��H���s��H��A����E��t�[L��H��1�]H�5T[A\A]A^��r��f���AVH�=<[AUATI��UH�͹SH�������t�H�="[L�������uH��[]A\A]A^�@�H�=AL��������
H�2H��H���SI��L�S��H��H�2I��H���,�L�������u�I�:L�L$L�$�q��L�$L�L$H��I�����L�$荑��L�$L��H��I��I�xP�'r��H�H���2���L�%GZL�5FZ�-H�ƹ
L�������tzH��H�H��H��������H��L����€���u�H�}1�����AE��@L�$�wp��M��H�
�YL�$H���ƺ[1�]L��A\A]A^���@H�}�
1��h�����I�E�j���@L�$�p��M��H�
MX���H��SCH��tH��SC��H��H�=�SC��x����uH��SCH���H�=QY�{��H��L�
	���A�Xj��H�ƿPH�
�����`��ZH�=PSCYH���g���H�@SCH���ff.���UH�5�XH��H��SL��H��8dH�%(H�D$(1��1e����uH�D$(dH3%(��H��8[]�f�H�T$H�5�XH��1���|���E���H��H���*f��1�H�ËD$����;S8t��S8��vH�sHH��t�H�C@H��H�P�e����H�|$��f��H�D$H�C@�k����|����AWAVAUATUSH��H��dH�%(H��$�1�谋��H��H�\$8H���e��E1�1�1��@4H�x1�I��H�D$8I���h��H���/H��跄��H��H��I���Ie��H���q���H��I���f���L��L���+e��H���ӆ��L��H���H���1�H��H��H�5iV��n����upH�|$8��H������H�D$8H��t#H�H1�H�V1����g��H�|$8�a��1�H��$�dH3%(��H�Ĩ[]A\A]A^A_�@1�H��H�5�VH���<n�����t���H�D$@I�t$PL�d$pH��H�D$�q��H�D$0H�D$H�D$(H�D$@H�T$H�t$H�|$�,x�����,L�l$(�J���H��L���/x��H��t�1�H��H�5�VH���m�������H�T$(H��H�5]NH������������H�D$0�'L����y���H��L��H��H�5�U�����������H�D$0H�=[H�p1��n��H��H��H�5�UH��I�����L��A���|��E���Y���1�H��H�5�UH���m����������8����H�D$8�D���fDL��H���c��E1�1�1�1�H����n�������1�H��H�5�UH���l��������H��1�H�5~UH���l�������ny��ff.���UH��SH��H��(dH�%(H�D$1�蛈��H��H���b��H��H���m����t!H�D$dH3%(u>H��([]�f.�H��H���U�H��H���z�����u�H���
c��H�$H�E���x��ff.���UH��SH������H��H����a���x4H�Å�uAH�{(����H�{����H�{ ���H�=gNC�P譑��H��H�@0H��[]��fD����븐��AVI��AUATUS1�H��@dH�%(H�D$81�H�l$L�d$蜔��I�vPH��I���
n��D1�L��H���Ku����t/H�t$L���Zu��H��t�H���-X��H��H���^��H����DL��H�5��H����g��H�L$8dH3%(u
H��@[]A\A]A^��w��@��ATI��UH��SH�P�s��H�}PL��H���s��H��1�H	�tH��tH��t"��\�,�[��]A\��[]��A\ú������ff.�f���H��LCH��t��H���_���1�H��1��#V��H��LCH������1��f����ff.���ATUSH��萈��H��LC��LC��uH�߾P�ҏ��I���j^��H��H��迏��H���j��H��H��謏��H�eH�
>I�T$H�5rH��H���H�
]���H���H���H��H[]A\�H�5LCH���1Z���m���ff.����ATI��U���PSH��H�=�KC����L���H��Pj�A�jH��H��L�
�B1�H�
�QH�5�Q�y��XH��Z[]A\�f���H�}KCH��tH�qKC��SH�=`KC�sp����uH�PKC[�fDH�=�Q�$s��H������H��H��L�
^���jA�8�0H��H�
W����2X���H��XZH����w��H��H�=�JC��JC���H��JC[�f���SH����s��H��H���^��Hc�JCH�H��t �Ӆ��H��H���]��[H������[�fD��USH��H���s��H��H����]��HclJCH�,H��t�~���H��H���]��H��諐���&\��H�=GJCH���w���H��H���H��[]��D��AUATUH��SH���*s��H��H���O]��Hc�ICH���[��H���^��H���L�#M��t2��L��H��I���]��H������H�;L���]��H���n����uH��[]A\A]�f�H��H��[]A\A]�.i��ff.���UH��SH��H���{r��H��tVH�H��tH;tH��H���z����t:Hc3ICH�,H��t�E���H��H���j\��H��袆����u.H��[]��H��H�UOH�5�O1�[]�eU��D�Z��H��H��H���\��H���k����t�H��H���\��H��[H��]�Xh�����AWAVA��AUA��ATE��U��SH��H��(dH�%(H�D$1��q��H����H�H��tH;tH��H���y������Hc;HCH�D9hu
D9p��D�hD�p�hD�`��Y��H��H��I���V[��H����~����ujL��H�߉l$H�$D�d$�.[��H��H���sT��H�D$dH3%(u{H��([]A\A]A^A_�@H�	NH�5�N1��T����f�L��H����Z��H���[��E���D��D��H����t���k���D9h�/���D9`�%����u����Cq����USH��H���Np��H��tIH�H��tH;tH��H���x����t-��X��H��H��H���JZ��H���i����u.H��[]��H��H�5MH�5�M1�[]�ES��DH��H���Z��H��[H��]�Wf�����SH���o��H��t>H�H��tH;tH��H����w����t"�MX��H��H���Y��[H�����f�H��LH�5�L1�[�R��f.���H���Co��1�H��H��1��CO����AWAVAUATI��H�=2MUSH���p��H�=4MH���p��H��H�51MI��H��LHD�1�1�I��$�H���$]���H��H���4s������I��$��o���1�H�5�LH��1���\����I��$�H���'R��I��$��Z���1�I��$��+~��H���y��H��H��LH�=�PH��1��c����H��H����Q��H������H��I��$���p��H�=cL��P��I�D$xM���f�����H�5TLL���f��H�I��H����I���E1�1�L�-+L�cDM�T�I�L�T$H��tf1�H�5LH��A���!y��L�$H��I�8�bp��L�T$I�:�Up��Ic�I�\�Hc�H��M��I�H��t*�	H��L��L�$�����t�A����f.�I��H�� l��L��1�H�5�KH��1��*M��L��I�D$`��l��H��ACH�5�ACH�=�ACH����_��I��$�H��[]A\A]A^A_�f�H���o��H��H�=K1��a��I��$����f.������H�=�JI����_��I��J���ff.�@��UH��SH�����D�WCCH�XCCE����H��P�Z���H��E1�E1�H��H�& 1ɺH�C H��
H�=�JH�CH��H�C01�j@j@jjjH�u�	b��H��0A�H�
rJH�pJH�5�JH�=�J��S��H�߾H����q��H��A�1�jA����H�dJH�5uJH�={J�|��ZH��YH�¾�q��H��1�A�jA����H�VJH�5hJH�=oJ�|��^�_H��H���^q���i��A�H��MH�5MJH��H�=KJ�6���H�߾H���&q���p��H�2JA�H�=7JH��H������H�߾H����p���|��A�H��MH�5JH��H�=JH���DŽ��H�߾H���p��H��A�H��IH�5JH�=J蔄��H�߾H���p��H��A�H�TMH�5�IH�=�I�a���H�߾H���Qp������A�H��IH�5�IH��H�=�<�)���H�߾	H���p���DX��A�H�MH�5�IH��H�=�I��H�߾
H����o��A�1�H�MH�5�IH�=�E�Q��H�߾H���o��A�1�H�MH�5kIH�=tI�}Q��H�߾H���}o��A�1�H�MH�5RIH�=E�KQ��H�߾
H���Ko����~��A�H�8IH�5LIH��H�=PI�#���H�߾H���o��A�1�H��LH�53IH�==I�M��H�߾H����n��H��L1�H�5)IH�=:IA��oM��H��H�߾[H��]�n��f�H�5i?CH���aM������ff.��H��H�(�����t1��|���H�pX�|���UH��SH��H��[��H���z��H�}H���*R��H9øHD�H��H��[]�@����H�z(������`���UH��SH��H���Oz��H�{H����Q��H���T��H��H����q��H��H��1ɉ�[H�5�<1�]�Ul��D��H��H��H��롐��ATI��UH��SH��H��dH�%(H�D$1�H�$��z��H��H���SQ��H��L��H���ET����t1H���H���"s��H�D$dH3%(uYH��[]A\�f��J��H�<$����c����tH�<$�>M���@H�$H�5K�1�H�H1��S�����tg��@��AVAUATUSH����H��H��I��I��M���'z��H�H��tH9tH��H���n����t[M����H��tu�u��H�UH��tH9tbH��H���on����uSL��JH�
�L�H1�H�5�F�|��f.�L��FH�
rL�F1�H�5�F�_|���H��H��L��L���|��H�5 ���H��H���UN��L���}s��H�~:CH��H���k��H�5tH���x��H��tH��[]A\A]A^��s���[]A\A]A^��L�'FH�
�K�G1�H�5�E�{����H��鳃��AWAVAUATI��UH��H��H��SH���QU��H���H��H���b��H���R����_��H��I���F��H���L��H���X��M��tpL����H��H��tcL���d��L��I���c��L��I���H��H�F9CL��L��H����Z��I��L��H��H�
@H��I������M��t'L���hs���fDI��H�
��L��1�H���a��H��H��[]A\A]A^A_�r����H��鼂�����ATI��UH��SH��H��H�� dH�%(H�D$1�H�t$H�D$��{��H��E1�1�H�T$A�H��H��RATjH�T$0�K��H�� ��u4H�t$H��H�D$�H��H�D$dH3%(uH�� []A\�D�H����`�����<d��ff.��AVI��H��AUATUSH��dH�%(H�D$1�I���vS��H�$H��I���S��1�L��H��H���sC��H��tnH��H����P��L��I���HH��1�L��H���Y��L��I����q��H����u��L��� q��H���e��L��H�L$dH3%(udH��[]A\A]A^���x��H�<$����^����tL��E1��m���f.�H�$H�EG�1�H�H1��CO�����c��ff.���������tÐSH�z(H���SE����t[�DH�{1�[�V��ff.��SH��H��H�5"�,1��&s��H�5�F�����[�ar�����LJ������uhATUSL���M��tOHLJ�L��f.�I�$H�{�H�CH��tH�{��H�޿�jz��H�mH��u�L���i��[1�]A\�f�1��ff.�f���ATI��UH��SH��H��dH�%(H�D$1�H�$�t��H��H���#K����p��L��H���K��H��H���y����t,H���H����l��H�D$dH3%(uTH��[]A\�@�D��H�<$�����\����tH�<$�G���@H�$H��E�1�H�H1��cM�����<a��ff.���������tB�����t8H��H�=7Ct2��I���7C�K��H�5�EH��H���N�����H�=�@�`��H��6CH��u�H�
�@H�JE�1���L���@��H��6CH��tH��6C��H��H�=�6C�[����uH�}6CH���H�=n@�4^��H��L�
9�A��j��H�ƿPH�
���HC��ZH�=86CYH���n��H�(6CH���ff.���UH��SH����z��H��H���3I��H�x`H��H��t
H�@`�Jm��H�{x�Am��H���H��5CH��tHǃ��m��H���H��tHǃ��l��H�{P�ea��H����Ya��H����Ma��H����ab��H�=r5C�P�x��H��H�@0H��[]����AVI��AUI��ATUH��S���z��H��H���YH��I�ă�ti������t{H�EH�8�wK��H��I�H�8�iK��UL�
�31�PA��1�H�
�>A�vH�$5�S��J��H�� []A\A]A^�@L���@W��A��$�[]A\A]A^��H�xPH��t
H�@P�R`��L���A��H�����H���_��I�D$P[]A\A]A^�DL����V��A��$�[]A\A]A^�ff.�@��SH����x��H��H���HG�������u�[���H��H�5�=H���J���[�D��SH���x��H��H���F�������u�[���KH��H�5�=H���J���[�D��SH���Sx��H��H���F���PH���F��H�5�;[H���{d��ff.���SH���x��H��H���hF���PH���[F��H�5;[H���;d��ff.���SH��H���H�t$(H�T$0H�L$8L�D$@L�L$H��t7)D$P)L$`)T$p)�$�)�$�)�$�)�$�)�$�dH�%(H�D$1�H�=�2Ct3H��<H�5C1���>��H�D$dH3%(ucH���[�DH��$��$H�D$H�D$ �D$0H�D$�w��H��H��H���g��H����v��H��H���DE��H�2C���[��fD��H�2C�@��H�G`H��tH�G`H���3i���ff.�@��AUATUH��SH��H���wv��H����H�UH��tH;tH��H����b������H���H���Hc�I���5;��I��H��t0DH�H�[H���1f�p��qf�p��q�If�p�f�H�H��u�H�uXH�}HH��uPD��L���`��L��H�EX��\��H��H��[]A\A]���H��H�(;H�5>A1�[]A\A]�1=����v��H�}H�D��H�G����H�G(����USH��H���^u��H��tyH�H��tH;tH��H���a����t]H�{(1��2`��H��H��u�4�H�[H��t#H�;�\����u�H�3H���Z��H�[H��H��u�H��H��[]�F9��fDH�L:H�52@1��[<��H��1�[]�f���ATUH��SH���t��H���\H�H��tH;tH��H���a�����<H�{htH�:[H�5�?]1�A\��;��@H�khH���$v��H��H�Cp��8��H�C(H��H���	h��H��H�C0��j��H�C@H���1_��H�CHH���5B��H�C8��j��H��I���<��L��H���VB��H�C��Y������H�C H��H�=#��.F��H�{H��E1�E1�H�����H�5V9�S��H�{E1�E1�H��H�:���H�5D9�nS��H��1ɿH�5�����<��H�{E1�E1�H��H�D���H�59�8S��H��1ɿH�5G����<���
C��H�
mJH��8H�5p8H����K����B��H�
KJH��8H�5�<H����K����B��H�
)JH��<H��H�58�K��H�{H��E1�E1�H��H�5�8�R��H�{(H��E1�E1�H���H�5�8�|R���Wc��H���?L��H��E1�E1�H�5�8H�8�H��H���MR��H�{�dn��H��H��H���[]A\��fDH��7����@H�{�^��H�C �I���fD��H�G`���������t
1���S��H��H�h����En��H�߉�������[�ff.�@��UH��AWAVAUATSH��8H�}�H�>dH�%(H�E�1�H����I��H��E1���A��H��A�VH�8u�A�FH��E�H��H��H��H��H��H���H)�H��H9�tH��H��$�H9�u�%��HH�\$E1�H���H�]�H�]��f.�K�|�D��H�E�E��H��H�H�;�A��E�GI��K�|�H�Cu�E9�thL�7H�
�;��1�H�5�5�gk���H��H9�tH��H��$�H9�u�H�� H�L$E1�L�t$�E�I���L�u�f�H�u�1�H�=�61�D�E��_��D�E�L�u�H��I��I��K�H��Z@��D�E�L��H�<;H�CH�5[6H�=2:�_��L���V��H�E�dH3%(u$H�e�[A\A]A^A_]��H)�H�L������T��D��H��1�H��H�5j2�o�����ATUSH��H�k H��H�(dH�%(H�D$1��d`��H�=�5H�$I����N��jH�s E1�H�{HA� �H�T$RH���L��XH�=�5Z�N��jH�{HE1�UA� H�¹!L����K��YH�=r5^�N��jH�{HE1�UH�s A� �!H���K��_AXH�D$dH3%(u	H��[]A\���S��f���ATI��UH��SH��H��H�(dH�%(H�D$1��rF��H�L$L��H��H����N���D$%�\�H�D$dH3%(u	H��[]A\��VS��fD��H�Gx����H��H�(�/L����tH���fDH���wf���SH����f����t[ÐH�{([��G��fD�������tÐSH��H�����H�{(H��t#�&5����t[�H�����H�{([����@��@�5����t�H�����H�{([����h��ff.��������uÐSH��H�h�cB��H�{(ǃ��4����uH�{1��F��H��[���H���(�H��t�H�{(�z4����t�H�����H�{(���Sh�����AWAVI��H�=Y3AUATUSH��HdH�%(H�D$81�H�L$H�T$H�D$H�t$�P������H�l$Hl$�V��H�\$I��H9�v H��L���ud��H���<��H�\H9�w�1�L��L�l$ �Td��H�=�2�^��H���1��H��H���D@H����A��H���H�D$ �x.t��H�x�
L���[��I�Nj��u�H�D$ H��t��8u�Ic�I9�u�H���?��A9�t�A��~�D��1����ba��H���JA��H��u�DH���`��L���d��L������Q����H���J��I�$H�8H���]E���;�K��H��1�1�H��1��~<��L���j��H�|$�7R��H�D$8dH3%(��H��H[]A\A]A^A_�@H�D$H�L5�1�H�H1��"<���H�t$ ��T����uML�l$(I���tB1�E���/������D9�������~����1��L`����f.����A��A����O��ff.����SH��H��`dH�%(H�D$X1�H�L$H�T$H�t$��j��H���D$� c��f��D$�*D$�D$H�CH�D$@H�D$ �D$�D$0f��*D$�D$8�D$4�%`���H���A��H�|$H�D$(H�D$H�j��H�D$XdH3%(uH��`[��N��D��AVAUA��ATI��U��SL�w0H�(�T��H���w_��H��tH��H���G��E��x)D��L����b��H��H���?��H��[]A\A]A^��L��� f����fDL���b�����ff.��������@�������uH��/H�5�31��m0��D�����u
�����t�f��������AUI��ATI��UH��SH���H���W��H���L�(H��L�`H�h�d�����H�����u
�����tH��[]A\A]�@H��H��[]A\A]�v�fD��SH���#h��H��t&H�H��tH;tH��H���T����t
H�CP[�@H�d-H�5�21��s/��H��*[�f.���AUA��ATI��UH��SH��H��dH�%(H�D$1��g��H��H���5��A����H�5�1D��Hc�H�>������H����D��f�H�D$dH3%(��H��[]A\A]ÐH���P��H��H���D����H�p(H���,N���f.�H�p0H���N���f�H�x(H��H�T$�_N���4$H���N���f�H�x(H��H�T$�?N���t$H���N���^���fDH�pH���M���G����H�x(�Ga��H��H���M���'����H�x(��W��H��H���|M�������H�ppH���dM������H�pxH���LM�������H���H���C�����@H���H���yC�����@H���H���aC�����@H���H����L���t���@���H���"C���]���DH�H�8�M7��H��I�$H�8�>7��SL�
o1�PA��1�H�
|*A�t$H�� �AU�6��H�� �����nJ��ff.���H��H��H����J�f.���H�������H��H��H�����f.���H���������ff.���USH��H���[��H� C� C��u]��Z��H��H���b��H�߾PH����b��H�fH�
�H�PHH�H(H�@H���H�H���H��[]�@H�5�CH���-���ff.�@��H��H��H����V��ff.���H�]CH��tH�QC��SH�=@C�D����uH�0C[�fDH�=�/��F��H���Y��H��H��L�
���jA�(��H��H�
�����+���H��XZH���pK��H��H�=�C��C�V��H��C[�f���ATI��USH��0dH�%(H�D$(1��>��L��H���1��Hc-�CL��H��/=��H�t$L����`[����H�}A�����9���\$H�]�T$�L$�D$�\$�T$�L$�$�eQ���\$�T$H���L$�$H���e��H�D$(dH3%(u	H��0[]A\��G��D��SH����=��H��H����0��Hc�CH�H�8H��tH��W��H�=�C�P�`��H��[H�@(��ff.�@��AUATUSH��H���Z@��H���R\��H�=cC�PI���N`��H��PHH�+�P=��H��H���E0��Hc.CH��pH�H��t�V��1�1�H�H��H��[]A\A]�D���L��I���e2��1�H�5�-H��pH���
=��H��xH��tL���H��p���AUATUSH��L�/I��pt}��H��H��E��u8��H���-������I��pI��uBL����Y��H��L��[]A\A]�V��f�1҉���-��H��H��I����G������I��pt�L��1��pM���fDH��H��,H�5-1�[]A\A]�I(��f���Hc�CH�<�LG��ff.������A�љA��D�ʉ�Hc�CH�<�n]��f.�@���ff.���UH��SH���.W��H��C��C����H��P�l^��H���DV��H��H���Y^��H�
�	H�	H�K H�5�H�
�H�SH��H�K(H�
#H���H��H���H�5gH��H���H�����C��H�,H�5� A�H�=/H����]��H��H�߾[H��]��I��@H�5�CH���(���#���ff.����UH��SH��Hc�CH��V��H���lE���S��uH�{tH��[]��E1�E1�H��H��H��H�5t+�>���C��ff.����UH��SH��Hc$CH�H�{H��t �s�h?���CH�{�(Q��H�C��,��H��H����,��H��1�[H��]�@=��ATI��UH��SHc�CH���U��H���D��H�;tf�s����H���f���H�;1��8��H�;�P��H��E1�1�UH�;L�
�1Ҿ�t4��H�,$H�;1�L�
����E1�1ɾ�U4��XZL�#M��tQL���3#��H�;H���(8��H�;E1�E1�H��H�eH�5K*�=��H�;E1�E1�H��H�V���H�54*�j=���S��H��H����+��[]H��A\��^��fDH��� >���C����@��H��1�����f���AWI��H��AVAUI��ATI��USH��Hc�CH��C*��I���)��H�;H���`+��H���(,��H��tH��H����#��I9�tH��[]A\A]A^A_�@�R��L��I���]��L��H���+��I����*��L��H���+��L��H���g;��L����!��E1�L��L��H�CA�H�����H�5)�I<��L��1��C�l<��L���Q���@��1�1�H��I��H���Y��L���pN��H���x[���sL����<���CH��[]A\A]A^A_����H�UCH��tH�IC��SH�=8C��;����uH�(C[�fDH�=b(�>��H����)��H��H��L�
>���jA�0��H��H�
7����#���H��XZH���PC��H��H�=�C��C�kN��H��C[�f���SH����\��H��H���)��Hc�CH�<�x+���SQ��H�=�CH���dY��H��[H��������SH���\��H��H���H)��HcICH�<�T���Q��H�=<CH���Y��H��[H��������AWI��AVI��AUATUS��H��8dH�%(H�D$(1��!\��L��H����(��Hc�CH�H�D$�P��H�=�CH���X����L��L����M���f��L�l$L�d$�d$H�l$$H�\$ �$$f�L��L��L���bN��L��H��H����Y���D$L���\D$ �X$�$�D$�\D$$�XD$�D$�A��I��H��u��
�f��ZD$�X��,�f��Z$�X��,��AN�AF�A\N�A\H�D$�,�H�8�D,��~O��H�D$(dH3%(uH��8[]A\A]A^A_��1�1���=>��ff.�f���AVAUI��ATI��USH��H�� dH�%(H�D$1��Z��H��H���G'��HcHCH�H�+H��t�%��H��H��I���"'��H���6����u6A�EA�$H�D$dH3%(uUH�� []A\A]A^�f�H�;L����&��H�T$H�t$H���3U��f��*D$�A$f��*D$�AE��L=��ff.����AVAUI��ATI��USH��H�� dH�%(H�D$1��Y��H��H���W&��HcXCH�H�+H��t��$��H��H��I���2&��H���5����u6A�EA�$H�D$dH3%(uUH�� []A\A]A^�f�H�;L����%��H�T$H�t$H���CT��f��*D$�A$f��*D$�AE��\<��ff.����USH��H����X��H��H���%��H�=�C�PH���_U��H��P(H��H��1�[]���f���AVI��AUATI��UH��S���tX��H��H���)%����t\H�EH�8�X(��H��I�$H�8�I(��UL�
z1�PA��1�H�
F#A�t$H��S�'��H�� []A\A]A^�L��I���].��[L��]H��A\A]A^����f.���AUI��ATI��UH��S��H����W��H��H���w$����tZH�EH�8�'��H��I�$H�8�'��UL�
�1�PA��1�H�
�"A�t$H�Q�S�&��H��([]A\A]�HcCL��H�4H��[]A\A]�<��ff.���SH���9��H��t>H�H��tH;tH��H����A����t"�V��H��1�[H��H�5�$1����fDH��H�5"1����1�[����ATUH��SH�H��PH��t�1J��[H�E0]A\���+3��H���#O��H���%��1�1��H��P�!��H�5�!H��I���;��H��PL��1��3A��L���;J��H��P1�1��7��H��P�I��[H�E0]A\�fD��ATUSH���PK��H��C��C��ug��!��H��H���R��H�߾PI���R��H���E��H��H���lR��H�eH��H�7I��$�H��H�E([]A\��H�5aCH���	������H�=CH��tH�1C��SH�= C��3����uH�C[�fDH�=�"�t6��H����D��H��H��L�
N���jA�8�XH��H�
�����ZH�=�CYH���QF��H��C[����USH��H���NR��H��H���s!��H�x0H��tH���H��H�E0H�={C�P�9Q��H��H�@(H��[]��f.���AUATUSH��H����Q��H��H���!��I���W;��H��H��I��� ��H���1#����uH��1�[��]A\A]�fD��.*����tx� ��H�=�
CH���P��H������t��C��H�߽H��� ��H���ZK��H��H���.��H��A�E(�6��I�}0H��1�A�E,�)��H����[]A\A]�@H�i �1�1��#��L��L���0 ��1�H�����H����[]A\A]�f���ATUH��S��P��H��H�����H���@��I���5:��H��H������H������H���j+��H�{0��A�����'��f��f��L���*[,H�s0f��f��*S([]A\�S��ff.�f���H���CP��1�H��H��1��C�����ff.���H��H�GH�
H�GH��H�G H�4H�G(H��H�G0�@��ATUH��S�G��H�AC�3C����H��P��N��H�1�H��H��
H�C H�
H��H�CH��H�C(H�	H�C0�/��H�߾H�^�w/��H�߾H�R�c/��H�߾H�J�O/��H�߾H�>�;/��H�߾H�7�'/��H�߾H�5�/��H�߾H�.�.��H�߾	H�(��.��H�߾
H�"��.��H�߾H���.��1�A�H�H�5)H�=3�a��H�߾H���9��1�A�H� H�52H�=;�/��H�߾
H���o9��1�A�H�'H�53H�=<���H�߾H���=9��1�A�H�(H�53H�=;����H�߾H���9���F��A�H�#H�54H��H�=9I����L��H�߾H����8��L��A�H�h H�5H�= �L��H�߾H���8��H��H�uE1�H�;CjE1�1�j1�H�=�1�S�a(��H��H�uE1�jE1�1�1�jH�=�S��	C1��6(��H�� []�v	CA\��H�5i	CH�������1���ff.��SH������I��1�E������H�LCH��1�1�E1�L�A�C�L�D�@��H��I9�t,�A�Q��u�� u����H��A��I9�u�@�f�f�҃�Oȃ�O�A��DO��*��Y�#�A*�A���f��DO�B�D��*ȸ�X��
�#�X�f/�wf/�#�
v
[Ã��K����H,�[Ðf��f�1��@SH����H�?@H��t-�@�s)����uH�}H�5q"1����1�[�fDH�����H��H��tpH���x��H�x�O&��H��tz�H�„�t(H�s��_u
H�KH��H���H���J��KH���u��[�H��H�5�!1��{��1�[��H�H�5�!1��[��1�[��L��H�
�!�z1�H�5��_F��ff.�@��AUI��ATI��USH��H���?��H��H������H��H�����L9�t)H��H�5�!1�����H��1�[]A\A]��H�59H���aI����t%L��H���M���H���D�H��[]A\A]�H�QH�5z!1��s��H��1�[]A\A]�fD��AUI��ATI��USH��H����>��H��H��H�����H���>��L9�t)H�:H�5� 1����H��1�[]A\A]��H�5YH���H����t%H��H�����H��L��[H��]A\A]�};��DH�H�5z 1����H��1�[]A\A]�fD��SH�� dH�%(H�D$1�H��CH��t+H�tCH�L$dH3%(��H�� [�f.�H�=IC��)����t�H�=�,��H��L�
����A��jH�ƿP��H�
������H�D$H�D$ H��H�l���H�D$XZ���H��H��H�����H��H�=�C�C<���C����9.��f���SH���SK��H��H���h���@|[�ff.�@��ATI��UH��SH���K��H��H���/��H�x`t [H��]�1�1�A\������@xH��L��1�H��H���C��H�5����H�C`H���[��H�߾P����H�5DH��H���4��H��H�5i�4��H��H�5��4��H��H�5��4���C|1�1��5�CH��[]A\���f���ATI��UH��SH���:J��H��H���O��H�x`t [H�0]�1�1�A\�����@xH��L��1�H��H����B��H�5����H�C`H���{��H�߾P����H�5dH��H����3��H��H�5��3��H��H�5��3��H��H�5��3���C|1�1��5�CH��[]A\���f���UH��SH���^I��H��H���s��H�xH���.��H�{ �-��H�{(�-��H�{0��-��H�{8��-��H�{P��-��H�{X��-��H�=rC�P�E��H��H�@0H��[]����SH����H��H��H�����H�xhH����A��H������H�߾P�CH����H�5�[H���2��@��H���H��1�H��H��1��
����SH���sH��H��t&H�H��tH;tH��H���2����t
H�Ch[�@H��H�5�1��
��1�[�ff.�@��SH���H��H��t&H�H��tH;tH��H���62����t
H�Cp[�@H�!H�5*1��#
��1�[�ff.�@��AUA��ATI��UH��SH��H���G��H��H�����A���|H�uD��Hc�H�>���H���X4��H��L��H��[]A\A]�#,��H�pH��H�9HD�H��L��[]A\A]�\"��@H�p ��f�H�p(��f.�H�p0�f.�H�p8�f.��p@H��L��[]A\A]�!���pD���pHH��L��[]A\A]�;,��H�5��n���@H�pP�a����H�pX�Q�����xx@��@���f.��pD���x���1��xx@���i���fDH�@01�H���T����8@����H�@81�H��u��3���H���
�����H�H�8�e��H��H�EH�8�V��SL�
��1�PA�1�H�
��uH���AU���H��([]A\A]�ff.���ATUH��SH���E��H���H�H��tH;tH��H���/������H���}��
��H�UH��tH9t(H��H���/����uH��[H�5J]1�A\�
���1��H��I��H�����L���e5��H��E1�E1�H��H����H�5%�"��H�����H�{hH��t(H��1�L�
��E1�S1ɾ���H�{hXZ�
5��H�khH�߾P����[H�5]H��A\�.��f�H���B���@��ATUH��SH���mD��H����H�H��tH;tH��H���.������H��tb���H�UH��tH9t,H��H���a.����uH��[H�5�]1�A\�T	��@�k0��H��I��H����
��L���54��H�����H�{pH��t�4��H�kpH�߾P����[H�5D]H��A\�-��H���ff.�@��AVAUA��ATI��UH��SH���sC��H��H�����A��wH�
�D��I��Hc�H�>��H�H�8���H��I�$H�8���SL�
��1�PA��1�H�

A�t$H�H��AU����H�� []A\A]A^��I�~P�'��H����H�5&I�FPH��[]A\A]A^��,��@I�~X�o'��H�����H�5I�FX��f.�H���H��[L��]H��A\A]A^�50��DH���(��[L��]H��A\A]A^���DI�~�'��H���O��H�5� I�F�c����I�~ ��&��H���'��H�5I�F �;����I�~(�&��H���
��H�5�I�F(�����I�~0�&��H����
��I�F0H����H�5�H���+��H�5������I�~8�O&��H����I�F8H����H�5�H���{+��H�5�����H������H�5�A�F@�t���H�����H�5?H��A�FD�5+��H�5��M���f�H�=�D��I�F0�A���H�=��,��I�F8�i�����ATUSH����@��H���/H�H��tH;tH��H����*������Sx��t`H�{`�%H�{h�9��I�ċCx��taH�k`�CxH�C`����1�L��H������H���0����[]A\�@H�pH�5�1�1��y����[]A\�f��CD��t8H�{p�9��L��H����
���Ņ���H�=s�o#��H��t	�8��L������PH�߉CH����H�5�H����)���Cx�1���f��H���3$���F���fD1�H��H�5(1�������[]A\�f.�H��H�51�1�����+���@�H�5�1��M��H���U��H��H���J��H��1�H���m�������1�H�5i�
��I�����H��H���
��L��H���/�����f.���USH��H���>��H��taH�H��tH;tH��H����(����tE�Cx��t^H�k`H��t}H�C`�Cx��tQH��1�1���
��H��H��[]�.���H��H��H�5�1�[]���D�S|��u1X[]�DH��1��"��H��H��[]�`.��H�]
�����H��H���
��H��[H��]�8��f���UH��SH����=��H��H����	��H�Ë@|��uqH�{`t2H���J=��H�{`t#L�K
H�
u�1�H�5��6��f�H��1��6+��H��1����H�=�B�P�k9��H��H�@(H��[]��@�c��H��H���X	��H���`7���r���f.����Hc��BH�H�G �ff.�f����ff.����ff.���SH���1��H�L�B�>�B�������H��H���8��H��H��H�
����H���H������PH���H����8��H��H�3E1�H�
E1��H�H0H�=�1�1�jjj�T��H�� ��B[��H�5��BH������^���ff.����H�u�BH��tH�i�B��SH�=X�B������uH�H�B[�fDH�=j�t��H�����H��H��L�
����jA�(�H��H�
��������H��XZH��� !��H��H�=��B���B�;,��H���B[�f���UH��SH������H��H���c��H��H�@ H�x����H�C H�x����H�C H�8H��t�u+��H�C H�H�=��B�P�	7��H��H�@0H��[]��f.���AVAUI��ATI��UH��SH���S��H��H������H��H�@ H�8H��t�+��H�C H�H�x�P��H�C H�x�3��L�s H�����H�k L��I����L��H�EH�k ����5��BH��1�H�E1�[]A\A]A^�j��f.���H�����1�H��H��1�������H�G H�8�����H�G H�x���ff.�@��H�G H�x���f.�D��SH���c.���M�B��u	[��H��H�56�B[���AVI��AUI��ATI��UH��S�9t�H�=�L���������H�EH�.�x���"L���T��H��H���H�} L���,��H����� ��%��L��H��H�E�@f��4��L��H�C�(��L��H�C���H�}H��H�C�.��H�sH�} H������H��[]A\A]A^ùH�=��L��������9����H�=�!L���H���������D�1�1�1��H��H��[]A\A]A^�@L��1�H�D1��� ��H��[]A\A]A^�@H���1�1����1���ATI��UH��H��SH������M��H��H��[E1�]1�H��A\�Q���AUATI��UH��SH��H� ����H��H��tGL�hL��L���P$����t M��M��H��H��
1�1�1��l��H��H��[]A\A]�fDH��1�H��
1���@��H��H��[]A\A]�f�AWAVAUATUSH��H�t$�G\t.M��I��I��H��I���v2H�J�1�1�H��
����H��[]A\A]A^A_�f.�H�WP�����H�t$H�H9���A���A)�H9�DO�H�C@H�sPH�@H��tH�(�UJ�L"H��H�� v%� �R��H�{@�H��H������UH��D�|A�M�P��H��H�|L��f�LL�����B�#�EH��[]A\A]A^A_�f�H�wPH�)H�5�
E1��6���H�t$H�L$H��A�H�����H�t$�-����AUATI��UH��SH��H�0����H��H��tGH�L��L�hL���]"����t M��M��H��H�<1�1�1��y��H��H��[]A\A]�H��1�H�L
1���P��H��H��[]A\A]�f�ATUH��1�S����UH��tZL�%(
�5fDH�CH�HH;KsRH�3H�K�H�H�C�H���U��t��"u�L��H��H���.���U��u�H��1�[]A\�_#���H�����H���������I�xt�@AWAVI��AUI��ATI��UH��SL��H��A�@����C�}�6�H�=i�H����������H�=PH���������H�=��H��������SL���t����"H��H������H���H�����L��I���H���L��L��H�=�H��1���
��H��L9�tL��H�D$�l��H�t$H�;H��H�S[]A\A]A^A_���DL�����L��L��H�=��1��
��H���DI�8I�PH�5M�M���������H��[]A\A]A^A_�fDL��L��H�=#1��L
��H���m���@L�����L��L��H�=H��1��!
��H���B���f�L���H���L��L��H�=�H��1���	��H������E1�H�
�
��1�H�5��+��ff.����H��BH��tH��B��H��H�=��B�@����uH���BH���H�=�����H��L�
	A�`j��H�ƿPH�
]�������ZH�=��BYH����"��H���BH���ff.���H�u�BH��t��H�����1�H��1����H�L�BH�������G\���‰���8�tR��SH��	ЈG\��t*H��H�5����v���H�5W�CX��[��#��@�X��0���CX[������������ATUSH���P��L�%q�BH�-��BH�CL��H���S1��H�C �*��L��H��H�C(�;1��H�C0���H�C8���H�
�H��H��H�C@H�5��H��H�C�xt&L��H�
���1�H�5��
)��DH�
�H��H��H�5����H�C�xt!L��H�
���1�H�5��(���[!��H�CPH�CH[]A\�ff.���UH�SH��H���7���H��t"H���!��H��H��H��E1�[1�H��]����H��[]�f���USH��H���T$H������H��t!H���� ��H�L$H��H��A�H���M���H��[]�fD��USH��H��H�T$H�{���H��t!H���u ��H�L$H��H��A�H�����H��[]�D��AUATI��H���USH��H���@���H��t;L��H����I��� ��H��M�EL��H��H��H��[]A\A]���f�H��[]A\A]�D��ATH��I��UH��H��H�=��S�������t�H�=�H���������uYH��L��H�����I��H��t;� �)��H�}(�`�H��H��L� �$��I�t$H�}0H��[]A\�N��fD[]A\�[H��]�1�1�A\������S��H���
���H��t�H�X[�ff.�@��SH��H������H��t�HH�X[�ff.�f���AUI��ATI��UH��SH���H���L��H�{8L�(H��L�`H�hH��[]A\A]�#��ff.���AVAUATI��USH��dH�%(H�D$1��H��A�D$\�I��I�D$81ۋP��t"H���L���H��H�p�I�D$89Xw����I�L$(L)�H�$�A����1�L�5�� f�L��1�1����I�L$(��9ivjH���H���C�t�H�H�r�6@��i��@��xu��t
H�CH9Ct�H�KA�L��L������H�C�K��I�L$(H�C9iw�I�D$H��L��L��A�H�H�P���H�D$dH3%(uIH��[]A\A]A^��t�C9C�>���H�KA�L��L����C�KI�L$(�C�������f.���H���#
���H���f���AWAVAUATUSH��XH�oHH�t$H�T$dH�%(H�D$H1�H�G@H�H�D$ H���I���H�D$ 1�L�d$0L�0A�9���f�CM�NH�D$@)D$0A�DD�{f������A�TH�I�UH�L��I�P�:���H��H�=����������H��H�=���������H��H�=��������D��I�pL��L�D$H��H�D$��L���}%��A�9��,���H�D$ H�@H�D$ H�������H�D$HdH3%(�H��X[]A\A]A^A_��K�l>�������@L��L�D$D���!��L�D$I�P�b���DG�|>�L���
L�D$�� ��D��L���3��L�D$I�P�*���DO�|>�(L���L�D$� ��L��L�����L�D$I�P��D�@L��L�D$(L�L$� ��L�L$L��M�L���n��L���&���L�D$(�\I�P�������ff.�@��AWAVI��1�AUATUSH��H�T$H�4$��H�5a�H��I���	"��I�V�B����1��]fDI�T$1�H��L��H�5��'$��M��tH�5�L����!��H�5$�L���!��I9\$tH���5
��I�V��9jvYH���"L�$�I�\$H���n�H��tH�����H��I�t$I�~0�@��I�Dž��e���H�5�L���F!���Q����L��H�5���1!��L��1����H�T$H�<$H��[H��]A\A]A^A_������AVI��AUI��ATUSH��H��H�� dH�%(H�D$1�H��H�4$H�5�L�e�D$H�D$L���y���u%H�L$dH3%(uaH�� []A\A]A^��H�5��H��L�����H�t$H��tL����1���L��H�5��H�������������	��f.�D���ff.���SH�����H���B�v�B����H�߾P�"��H�zH�P0�1	��H��H���"��H�
oH��H���H����5��H�3E1�E1�P1ɺH�=��j@1�j@j@j@jjj�X���H��8H�3E1�jE1�1ɺjH�=v�j���B1��*���H�� ���B[�fDH�5��BH������6���@��UH��SH��H�����H��H�����H��H��H��[]� ��ff.�@��H��H�=�����H�5�H�������f.�AWAVAUATUSH��H�$H��xdH�%(H��$h1�I���{��I�\$@H�D$H����H�D$`L�|$(H�D$H�D$0H�D$�tDL��H���-�H�����H�T$H�t$M����lj��J%�����
H�|$01�H������!�������H�>��1���H�[H��t[��H�+I��L�mM��t
I9E�s���L��H��������`���I�}���H�@��1�H��1��G�H�[H��u�H�\$1�H�����1�H���5!��I�L$I�<$1�M�L$(M�D$ PH��A�t$8�5��B1��:���XH��Z���H��$hdH3%(uQH��x[]A\A]A^A_���H���1�1����
���@H�|$0���H�|$H���a������g���H�GH��t7USH��H��H�(H�o H��H������H��H�CH��[]����f.��ff.�@AUA��ATUSH��H��H�/L�e I9�tH�}H�����H�EH�sH�{�w!��E�����H�5�1����I����!��H�{HL����1��. ��H�{���H�{ �|��H�{(�s��H�{0���H�{8�a��H�5b�BH�{@1�����H�{@���H�{H����H���5��I9�t(H��[]A\A]�DH�{H���������DH�E H�}�w�H�} u�H��H��[]A\A]���ff.���H��H�H9x t1����1�H���5��BH��1�1�����1�H����������f���H�]�BH��tH�Q�B��SH�=@�B�s�����uH�0�B[�fDH�=���$��H�����H��H��L�
n���jA�0��H��H�
g����2�ZH�=��BYH�����H���B[����AWI��AVM��AUATI��USH��8H��$�H�D$pH�L$ H��$�L�l$xL�L$H�T$(H�L$H�t$H�$���L��H������PH���!��L��H�(H�����H�T$(H�CH������H�L$ H�C H�����L��H�C(��L�L$H�C0L�����H�<$H�C8����H�5��B1�H��H�C@���H�L$1�L��H�T$���1�H��L��H�5o���H�CHL�k���H�}H��H�C���H�EH���6�H�} tH��8[]A\A]A^A_�H��8H��[]A\A]A^A_�:���f.���AUI��ATI��USH��dH�%(H�D$1�H��H�$�,��1�H������H��tkH�����L��H����1�I��E1�1�H��H����H�4$I�D$(H��tL����H�����H�D$dH3%(uRH��[]A\A]�f.�H�4$H��tL�������D����1�H�:���1���H��H�$H��t������ff.���S���1�H��1���H���r��H��[H����fD��SH��H�H��t1�H�5������
��H�CH�{ H��t
�����H�{(H��t
����H�C([�ff.���SH������H��H���8�H�����H�=��B�P���H��[H�@0��D��U��SH��H�����H��t7H�H��tH;tH��H�������tH�{ H��t2H����[]�e���DH��H��H�5^�1�[]���DH��H�%�H�5>�[]�����SHc$�BH�H�_���H�[���SH���������B��u	[��H��H�5��B[����AVAUATA��USH��H�_H�l$@H�L$HH�{tXH�<$H��t?��H�<$H��1�A��H��H�
��BA�H�D$@H��1�[]A\A]A^�R��f�H��[]A\A]A^�A��A��1�H��D�$L�L$�W��H�5P�BH��H����L�L$L����D�$D�s(D�k,D�C4D�c0H�C�+��H�;I���
��L��H���E�H���m��H���%��H�����H��H��E1�A�H��H�5|����H��H��[]A\A]A^�������AWI��AVA��AUA��ATUH��SH��H�_L�D$H�;���L�D$L��H��1�I��L���i��H�5��BH��H����D�s(H�D�k,H�C0�L��H�;I���1	��L��H���f�L��H����
��H��H��E1�A�H�uH�5�����H��H��[]A\A]A^A_���AVAUI��ATM��UH��SH��L�w���L��H����L��H�������tH��tI�F(H�E�H��tI�VH�[]A\A]A^�f�AVAUATI��1�U��S��H��@L�wL��D�D$L�l$ D�L$dH�%(H�D$81��T$ L��L$$H�L$D�D$(L�D$D�L$,�a�����|$��H�L$H�T$L��L��L�D$�d�D�L$�t$���D$D�D$��H�|$���I�F �i��I�F�D$��~(1�1�f�H�D$��H�<(H���+���9\$�H�|$�k���H�D$8dH3%(u&H��@[]A\A]A^�fDH�D$H�8��I�F ��@�����AWAVAUATI��H��UH��SH�����H��H�@�P(D�x4D�p0D�h,�T$�
��L��H���F��T$E��E��D��H��H���n���H��E1�L��UL�
����1�1Ҿ��XH��ZH��H�o1����H�5qH��H����
��H��H��[]A\A]A^A_�	����AWAVAUATI��H��USH��H������H�hI���
��L��D�},D�u(H����A�A�L��D��D��H�����H�}�[���H���#
��H��L��E1�SL�
����1�1Ҿ�S�XH��Z����H��H��[]A\A]A^A_�N��ff.�AWAVAUI��ATUSH��hH�t$H�T$dH�%(H�D$X1��
��H������H��H���]�H��I����
��H���XH�|$H�����H�T$$1�L��H�t$ I������T$$�t$ L��������=H�T$,H�t$(L����H����H��A���x���F����D�D$A���D$Hc����D�D$H�߾�H��I��D���>��D�D$�L$1�D��L�����H�T$8H�t$0L��H���c����K�f.D$0����f.D$8����L���/��T$ f�H��H��+T$(�D$$f��+T$+D$,H��+D$�*��*����H����
��H������H�����L���I��L������H�D$XdH3%(ujH��h[]A\A]A^A_�f�L�������fD�D$ H�t$@H��D�|$H�D$@�D$$�D$D�D$�D$L�6�H����<�H���Z�f(��,��� ������f���AWI��AVAUATUSH��hH�<$H��dH�%(H�D$X1�����L�hI��H�D$I�}���H�T$H�t$H��H�����D�d$�\$��	��H�<$H���k�1�1�L��E��A��H�����H���������D$A�M8I�E(A�E0�D$A�E4����H��E1�L�
3���1�AWH�|$1Ҿ��XZL��H�|$H�]
1��
��H�5_H��H����	��H�����H�D$XdH3%(�4H��h[]A\A]A^A_�f����H��I���`���A��x+H�\$ D��H��H��A����H��L�����A���u݋D$H�|$01�H�D$0�D$8�D$�D$<�x�L��H��H���J��L�����I�} ���I��H�D$@I���L@��L��H����H�f�f��L��f���*D$@f���*\$L�*T$H�*L$D��L���p��H�����9�|�L�����H������Z����I�U0I�} 1��y����c����/���ff.�@��AWAVAUATUH��SH��XH�<$H��L�|$ dH�%(H�D$H1��z��H�XI��H�;����H���s�I������L��I�����L��H�����H�T$H�t$H��I������L��L���5���s<���
�,L$�T$ �D$$�S(�C,)ʉT$0�,T$)ЉD$4�D$(�C0�D$8�D$,�C4�D$<��L��H�����H����H�D$��H�|$H���o��H�t$0H����H�C ����K8H�C����H��E1�L�
���1�UH�|$1Ҿ�8�XH��Z1�H��L����
��H�5�H��H�����H���(��H�D$HdH3%(��H��X[]A\A]A^A_�DL��L��L���2�����DL������tH�s(H�S0H�{ ����L���fDH�t$L���s����tf��ZD$H�{ f(�����fD�D$�?�������f�H��dH�%(H�D$1��w�1�H�T$H�5��H��1��D$�g����D$����H�L$dH3%(u��H�������AVAUATM��UH��SH��H�_H�{tOH�|$H��t5�`��H��L��H��A��H�|�H�
��BA�PH�|$1�����XZH��[]A\A]A^�L��I��A��H��1��	��H�5N�BH��I������L��L�5���\��C8H�C���H�;H�����H��H�����H�;H����H�����E��u6H��L��L��A�E1�H�����`�H��H��[]A\A]A^��@�c��u�z�����t�C8�DL�5��ff.�@��AWM��AVAUA��ATA��UL��SH��H��L�wH�L$I�>�N�H����I�~uH��uJH��t2����H��1�H��A��H�
�H�
N�BH��PA�1���XZH��[]A\A]A^A_�@H��L��1�H�����H�5�BH��H���^��H�|$���E�n<I�FE��uwE�f8���I�>H���s���H��H�����H������H����H������H��H��E1�A�H�
���H�5�����H��H��[]A\A]A^A_�H������E1�A���v���ff.�f���H���BH��tH���B��SH�=��B�����uH���B[�fDH�=u��t�H��L�
Y�A� j��H�ƿPH�
]�����@H��XZH���&�H��H�=D�B�F�B�A���H�2�B[����AWAVAUATUSH��H��H�<$�b���H��H���W��H���^H�XL�cL������Ņ�tpL������L��H���E�H�C�|���E1�E1�1�1�1�H��I�����L��H�����H��I������M���H�<$H��1�[]A\A]A^A_�-�D�����H��H�D$����u�����H��H�D$����t�H�5]�L���~�H����L)�L��H���W��I�ƅ��<��L��H�= �1��Y�I��H�|$1�L��1��U��L��I�����L������I���z���1�1�1�L��I�����L��H�����L��I���#���M����L�kL���~�H�{ ����H�{ ���
�H�{ A��1҉�1��X�H�{H�5��I���%��H��H����H���jE1�1�UL��L�
g�L��PH��H�F�P1��6���H�� L������H�����H�<$1���@����H��L��[]A\A]A^A_�Y���f�L��H�=��1���I������L��������L���H�
���1�H�5���/���L������I���T����H�{H�5��� ��H��������ATI��UH��SH���z���H��H���o��H�X����L��H���[��1�H�����H�����H���o���H�{ H��t
H�C �y�H�{H��t
H�C��H�{H��t
H�C��H�{H��t
H�C���H�;���[]H��A\�����AVM��AUI��ATI��UH��SH��H�5��BH�������u"H���H�5��1����[1�]A\A]A^�f�M��L��L��H��H��[]A\A]A^�D�@��AVM��AUI��ATI��UH��SH��H�5��BH�������u"H���H�5��1��M��[1�]A\A]A^�f�M��L��L��H��H��[]A\A]A^���@��AVM��AUI��ATI��UH��SH��H�5��BH�������u"H�[�H�5$�1�����[1�]A\A]A^�f�M��L��L��H��H��[]A\A]A^�d�@��AVAUI��ATI��UH��H�5��BSL�wH���8����u$H�5�H�5v�1����m����[]A\A]A^�f�����H��H��� ��L��H��������t�I�~ ����t"L�#�H�
$��|1�H�5���A����M��t�I�~ ���PA�T$�PA�$�PA�T$�A�D$��[]A\A]A^���H����1�H��H��1��c�����ff.���USH��H�����H��B��B��umH�߾P�0��H���x���H��H�����H��H�
�H�U0H�5�H�mH���H�
�H���H���H���H��[]�H�5��BH������ff.�@��H�m�BH��tH�a�B��SH�=P�B�S���uH�@�B[�fDH�=����H�����H��H��L�
���jA�@��H��H�
������ZH�=�BYH�����H��B[����AWAVAUI��ATU��S��H�����L��H�����I�ċ@89��F�+)�9�Bڅ�uH����[]A\A]A^A_�M�|$ ��L�����4+L��I�����I�|$0H��H��L)�H�WL��M)�H)�H�L$����H�L$�ډ�A)\$8L��I)�Mt$0���fD��AWA��AVI��AUI��ATA��USH������L��H���2��D��L��H���D�H�S0H�s(L)�H��H�DH9���H�K D��H��H�T$H�$��H�$H�T$I��H��I)�J�|L)�L�$H��Z���L�$H�{ H��L��L����H�C Hk0D��Dc8L��D��L��H�k0�(���H��D��[]A\A]A^A_�DH�C(H����H�C( H�� ���@H�s(H9�vffDH��t�H�H����v��H�C(�����H)�H9�s5L��L��H)�H�����0�L��L)�H��H��������A���H�{ ��H�S0H��H�C ��������f�� ��f���SH���3��H��H�����[�@8���UH��SH��H�����H��H���`��H��tH�P0H�H�@ H� �H��HD�H��[]�ff.���UH��SH�����H��H�����H�x H��t'H������H�C H�C(H�C0�C8H�=��B�P����H��H�@0H��[]����H���S��1�H��H��1�������ff.���USH��H�������P�B��ud�?���H��H���T���H���|�H��H���A���H��H�
H���H�EH���H�
�H��H���H��[]�f�H�5�BH�������ff.�@��AUATI��U��SH��H��8dH�%(H�D$(1����H��H�����H��������L��H��I�����L��H��L�����H�����H��t6H��L�d$�fo$H�߉�L��)D$���H���Y��H��H��u�H�D$(dH3%(uH��8[]A\A]�������AWAVI��AUI��ATUSH��H��8�D$dH�%(H�D$(1���H��H������H����H�t$H��H�D$���H����H����f��H��A��l$L�d$$H�l$ �l$�2��L$ �T$$�L$�T$H��E1��u���H��H��t]�D$L��H��H���y���E��u��D$ �_D$�D$�D$$�_D$�D$�fDf��t$�t$�M��t�t$�AuM��t�|$�A>H�|$L��L�����H�D$(dH3%(uH��8[]A\A]A^A_��k�ff.���AWAVI��AUI��ATUSH��H��8�D$dH�%(H�D$(1���H��H���o��H���w�H�t$H��H�D$����H����H����f��H��A��l$L�d$$H�l$ �l$�2��L$ �T$$�L$�T$H��E1����H��H��t]�D$L��H��H�����E��u��D$ �_D$�D$�D$$�_D$�D$�fDf��t$�t$�M��t�t$�AuM��t�|$�A>H�|$L��L����H�D$(dH3%(uH��8[]A\A]A^A_����ff.���ATA��UH��SH���z����tNH��t���H��H�����H��H�������u��H��H��H������H�����us[1�]A\�@��H��H�����H�����H�����H��t�H�H��tH;tH��H������t���H��H���w��[D��H��1�]H��A\��H��H���U��H�����[�]A\���H��BH��tH���B��SH�=�B������uH�ضB[�fDH�=G����H����H��H��L�
>���jA�(��H��H�
7������ZH�=��BYH���a�H�z�B[����Hc}�BH�H�G0�ff.�f���SH����5U�BH�V�B����H�߾P�A���H��E1�1�H��H��A�����H�C H��H���H�CHH�[H�5��H�C0H�=!�j��ZH��YH�¾���A�1�H���H�5�H�=����H�߾H����H���1�H�5�H�=k�A����H�߾[H����H�5i�BH���I������@��H�E�BH��tH�9�B��SH�=(�B�����uH��B[�fDH�=�����H���\���H��H��L�
n���jA�8��H��H�
w�������� H��XZH���`�H��H�=��B���B�{�H���B[�f���USH��H�����H��H�����H��H�@0H�x�3��H�E0H�x�&��H�=o�B�P�e���H��H�@0H��[]��fD��AUATUH��SH��HdH�%(H�D$81��*���H��H���/��1�H�T$H��H��H�5J�1�H�D$0�k�H�|$�O�*���I�����H�|$H������H����L��H�k0H������H�|$H�E���H�k0H�}�3�1�H�EH�C0H�8H�P����(��H�S0H��H�:���H���A��H������I�����H�S0H��H�:�b��H�����H��H������H�5��H���0��H��I�����H�T$0E1�1�RL��H��I��������H�T$0RH�T$0RH�T$$RH�T$8RL��j���H��0H��A�����H�|$0A	�tOH��t���H�D$8dH3%(uVH��H[]A\A]�f.�H�!�H�5��1��˽����f�H�|$H�|$0u�H�|$ u�H�C0H��P����f.���AUI��ATI��UH��S��H���2���H��H���7����tj������t|H�EH�8�X��H��I�EH�8�I��UL�
z�1�PA�w1�H�
v�A�uH���S���H��([]A\A]�fDH�@0H�pH��L��[]A\A]���fDH�@0L��pH��[]A\A]����H�@0H�p�ff.���SH���3��H��t>H�H��tH;tH��H���f���t"�-���H��1�[H��H�5\�1�����fDH�)�H�5��1��;���1�[����AWAVAUATI��USH��H��H��XdH�%(H��$H1��z���A�ƃ���v
A���%�@��H�S0H��H�:���H���Y��H��H����H������H�����H��I���2��H��H�D$�%���H�����H��H����H���j��H�����H�t$H�T$H��I���M�L��DŽ$�H��$L��$HDŽ$��H�߉�H��$�L��H��H��$��������$ 蜽��1�I��H��DŽ$8��L���ЋT$�$ ���$(1҉�$$D$��$,H�H��$0�P���A����H��$L��H�l$@L�|$HH�D$XH��$ H�D$PH�D$`H��$(H�D$h�D���L���D$xL�d$ �D$p�;�1�1�M����H��L���D$ �D$t�ʼ��A���PL��I��1�1�DŽ$�H��裼��H�|$��H��$HdH3%(�H��X[]A\A]A^A_�H��H�5"�1�諹���f�H��$L��H��$�L��$�H��$�H��$ HDŽ$�H��$�H��$(H��$��<���L��L��$�DŽ$���$�DŽ$����M��1�1�H��L�$�趻��DŽ$�M��1�1�H��L��虻������@H����1�1��k������fD�D$ ��1�����USH��H��Hc-t�BH�H�o��H�
�1�1�H�E1�H�k���H��E1�E1�H�EH�CH�NH�5#��@�H�8���H�CH��E1�E1�H�4H�5	�H�8H��[]�o��ff.�@��AUI��ATUSH��H���w��I�����L��H��贾��H��H������H�����L��H���&���� �l��E1�E1�L��L�(H��H�&H��L�`H�5��H�X����L����I�EH��H��H�x�D��H��L��H��E1�[E1�]H�A\H�5H�A]���ff.���AT1�L�
�E1�U1�H��SH��H��V����XH�CH��ZH�x����L�`H�����H��L��輽��H�����H�EH����H�MH��1ҋ5��B[1�]A\���ff.���SH��H����H�{H��t$H��1�E1�E1�S1ɾ�]��H�{XZ�r�H�޿ [��@��ATUSH����H�)�B��B����H�߾P���H��H�tH�E0H��H�EH��H�E ��H�3E1�E1�PI��1ɺjH�=��1�jj���H�� H�3E1�ATE1�1ɺjH�=��jj���B1��m��H�� �w�B�^��H���H�5��A�H�=��H���k��[H��]H��A\�7���H�51�BH��������ff.����SH����H������H�{[H�HH�PH�?H�pL�@�N���ff.���UH��SH��H��H�BH�x���H��t6H�HH��t�5��B1�H��1����H�CH��H�xH��[]�m��DH��H���H�56�1�[]鍴��ff.�f���UH��SH��H���k���H�{H���/���H�������tH��[]�@H�f��f��f����H�P�B
�*��B	�*��B�*��^��^��^��t�H��H��虻��H��H��辻��H��H��[]�����H���BH��tH���B��SH�=��B�C����uH�p�B[�fDH�=������H��L�
���A� j��H�ƿPH�

��������H��XZH�����H��H�=�B��B���H��B[����AUI��ATI��UH��S��H��蒲��H��H���׹����tZH�EH�8����H��I�$H�8���UL�
(�1�PA�v1�H�
��A�t$H����S�V���H��([]A\A]�H�pH��L��[]H��A\A]���ff.���AVI��AUATI��UH��S�����H��H���)�����t\H�EH�8�X���H��I�$H�8�I���UL�
z�1�PA�c1�H�
Q�A�t$H���S註��H�� []A\A]A^�L��I�����H��tI�U��B[]A\A]A^�fDI�E�@�[]A\A]A^�ff.����USH��H������H��H���c���H��H�@H�8���H�EH�x�g�H�=P�B�P�&�H��H�@0H��[]�����H���ð��1�H��H��1������UH��SH��H��H�GH�8����H��H��E1�H�5R�H��������H��H��H��[]������H�5������AVI��H��AUI��ATUH��SH��dH�%(H�D$1�H�$��H��L��H��I��H���#��H�<$t���H�<$����G������H���7��H��L��1�H����H�<$H��t踰��H�<$��������ttH�����H��t�B��H��H���׶��1�1�H�����1�H��L��@�����H��tH������M��tL������H�D$dH3%(u.H��[]A\A]A^�H�4$L���t����f�H�4$L���d�����
����H�������H�������H�������H���������@��ATUSH��tsH��H��I�����H�H��tH9tH��H���
����tIH��1�H��L����H�5"�BH��H���G���H�5 ���H�����H��t,H��[]A\�����[H���]1�H�5.�A\鷮��[]A\���H���1�fD��SH��f��f�H��L����H�����1�[�f���1���S��1�����o����x�t1�[���1�H���1��ɷ��1�[�D��U���PSH��H���״��H�5��H������tH��t_H��[]��H��t�H��L�
�E1�1�j1ҾH��蚼��X�PZH���{���1�H��H�5��H��[]����@H�����E1�E1�1�H�5k�H�������PH���5�����ff.���ATUSH��@dH�%(H�D$81�H����I��H�����I�$H��tH9tH��L���������H��L��H�������$H��H�C$�D$(�(��VH��(��_��]�(��V(�(��_��]�(�(�H9�u��m�u�e�UH�D$8dH3%(u'H��@[]A\��H�_�H�5�1��[���������@��H��S㥛� USH��H��(dH�%(H�D$1�H��H��H��H��H��?H��H)�H�$Hi��H)�Hi��H�t$�C���H��t6H��H��H���0���H��H���u��H�L$dH3%(H��uH��([]�H�=X�蜻��H�����"��f���S�h�����f�����=:�0t
=��0u.���K���$I�[���
�����)Ѝ�)‰�)��H����1�1��Ӵ��뾐��ATUH��H�=.�S�I��H��H��ti1�H�ƿ �"���H��H��藮���H��I��跰��1�H��tH)�H�P1�H�����L��H���d���H��tH������H��[]A\��1��A����H��I���a���H��u�H��1�1�����L��H������H��[]A\�ff.�f������������f���ATI��UH��H��SH���ײ��M��H��H��[E1�]1�H��A\�M���ff.�f���UH��SH��H��H��(dH�%(H�D$1�H�T$H�t$�.��1���tH�|$H�t$1��'�H�|$��tH�L$dH3%(H��u1H��([]�@�������H��I��1҉�H�
�1����1�����D��ATUSH��tsH��I��H�����H�UH��tH9tH��H���,����tHH��tc���H��H�H��tH9(tH��H�������t?H��H���ԯ��[L��]H��A\�u��DH���H�52�1��ۨ��[1�]A\�@H���H�5�1�軨��[1�]A\�@����1�����u�����>����H���։�D��jE��5��B�D$0PD�L$0藪��H��(�f���AUATI��USH��H��8dH�%(H�D$(1��w���H������L��H��I����H�T$H�t$H������L��L���Ԯ��H�t$H��跦�����of���t$�,D$����*K��-��(�)��,D$�YΉL$�K)��L$(�T�.�v,�,�f��(��=V�U�(��*����T��X�V�f��,��*C�D$�Y�(�T�.�v,�,�f��(��%
�U��*����T��X�(�V��,��D$茲��H��H�����H�t$H����H���ܲ��D�l$D�d$H��誽��H��H��语��D��D��H���1���E1�E1�H��H�!���H�5�H������H��誷��H������H��H�T$(dH3%(u/H��8[]A\A]�@H�)��1��D$�?�����q�������ff.���AWAVAUATUSH������/���H��H�?D��E��A��A���D$A���Y��D�����m���D$I��H���Z�f(���L������H��A�G�H�@L�|�@H��H���,����S�C�f�f��H��D)�D)��*��*�����H�s�f��H��f����H�����H�����L9�u�H�����H��L��[]A\A]A^A_�DL�u�H�
b��1�H�5o��_���L�p�H�
:��1�H�5G��7�����AUH�=�ATUSH��(dH�%(H�D$1�I�����H��H�����H���Ȳ��H��H��tpH�$�{.t��ݡ��H�{�
L���I�����H��A�E��u�H�$H��t��8u�Hc�H9�u�H��躰��9�t�1���������t�f�H�����1�1�H�-��@�í��H�D$dH3%(uaH��([]A\A]��H�������u/H�l$H���t$��~�1��@��9�t�1����X�����t��f���v��������-���ff.�f���SH��H�� dH�%(H�D$1����H��1�H��t����H�L$H�T$H��L�D$H���6���H�t$dH34%(uH�� [����ff.���SH������H��[H������USH��腠��H��H��t&H���U���H������H��H���ҽ��H��H���G��H��H��[]�ff.�f���ATH��I��USH�����H��tH��H���j����-��BL��P�W���H��1�[��H��]1�A\�R���f�ATUH��SH��H� ����H����H��I������H�{ H�����H��L���2���H��H��E1�SL�
�
1�1Ҿ��H��E1�1�L�
#1ҾH�$�Ӱ���5�BXH��Z1�1�迱��[L��]A\����[]A\�ff.�ATUSH��tOH�����H��1�H�5o�I��1����L��H��H���{���H��H��tH���K���H�����H��[]A\�1�H��H�5(�1��Q���H��[]A\�f���H�%�BH��tH��B��H��H�=�B蠹����uH���BH���H�=��T���H��L�
yA�(j��H�ƿPH�
-�h���ZH�=��BYH���7��H���BH���ff.���AWAVAUATUH��SH��H������H��H���H���H��H������H����I��L�-�	L�%�L�=O�@H��I�M��E1�U1�1ҾH���	���M��E1�1�1ҾH��H�,$���XE1�ZE1�H��L��L��H���E���E1�E1�H��L��H�5�H���*���M�vM��u�H��[]A\A]A^A_�f���UH��SH��H�����H��H���p���H��H��H��[]����ff.�@��SH������H��H���8���H�x �O��H�=`�B�P���H��[H�@0��@��AUI��ATI��UH��S��H�����H��H�������tZH�EH�8����H��I�$H�8����UL�
8�1�PA�W1�H�
��A�t$H����S�f���H��([]A\A]�H�pH��L��[]A\A]�"���f���SH������H���B���B����H�߾P�1��H��H�'���H�C H����H�C0�C���A�H���H�5��H��H�=�����H�߾H�����{���H�����SE1�E1�jH��1ɺjH�=R�1�j记��H�� �ܒB�O��H��E1�E1�jH��1ɺjH�=4�1�j�w���H�� ���B[�H�5��BH���9�������@��SH� �B���H��H��tH���B���H��[�ff.�f�ATUH��S�T��H������H���d���H��H��u ��H��訩��H��H���|H��������u�H��H���%���I��H��t]H��L���b��L��H�����H�}I9�t-H��u~L�eL��蝚���PH��萣��H�5�H���q���[L��]A\���DH�}H��t7���H�EH��P�M���[H�5ؔ]H��A\�*���f.�[]A\��V���x������H�����@��AUATUS��H���+��H������I��H��tIH���L�eL���D���H��t9tKH���
fD9t<H�RH��u�H�����H�mH��u�E1�L�����H��L��[]A\A]�f�H���h��M��t��Ґ��H�����H���K���H���s���H��H���w����AWAVAUI��ATI��USH��H���
�H��H���ŧ��H��u�H�������tH��茻����tI�} H���ܴ��H��H����H���h���H�߅������H����H���'���H��H����I�} H��L��蜫��L��L��E1�E1�H��H�5>����L��L��E1�H��E1�H�50��޲��L��H���C����5y�BL��1�H��1�[]A\A]A^A_�C���L�����H��� ����p���H������H��tH���s���H��H���L����"��H��H��跭��H��H��I�����H��� H���ȗ��H��H������H���D�����u#H���h������t��L��虞��H��H���e���H���5���I��H��tJL��襴��H��H��t:DL�uL�����L��H�������uL���e���H����H�mH��u�H�����H��tKH���N��I��H��t3I��I�.H�������uI�} H�����H��H�������M�vM��u�L������H���諿��H��H������H��[]A\A]A^A_�H���(���H��H��I���j��H�������L��H��薯��H�������L��H��肯��H����������H���\���H��H�����������ff.���AUATUSH��H���Z��H������H���:���H�
��BH�5<�B1�H�=�BH��諧��E1�E1�H��H�C H�W���H�5͎H���X������H��軶��H���3��H��蛗��H��tEH��H�}���I��H��tI��@I�4$H���L���M�d$M��u�L���j���H�mH��u����H���T���H��I������H��E1�E1�H����H�5*�H��H��路��L��H��E1�H����H�5ҕE1�蘯��H��H��H��[1�]A\A]������UH��SH��H�����H��H�����H��H��H����H��H�����H��H��[]���f����f.���UH��SH��H���+��H��H��耝��H��H��H��[]�/���ff.�@��H�m�BH��t��H������1�H��1�����H�D�BH�������ff.���SH��H�=��B�P����H��[H�@0��ff.�f���AUATUSH��H���Z��H�{�B�m�B����H�߾P���H�����H�P0訲��H�3E1�E1�P1ɺH�=��j1�jj�c���H�� �يB�t���H�3E1�E1�P1ɺH�=\�j1�jj�/���H�� ���B�@���H�3E1�E1�P1ɺH�=3�j1�jj���H�� �y�B����I������I��蜬��H����H��H�3E1�AUE1�1ɺATH�=�UP1�jjj覧��H��@�(�B跱��H�3E1�E1�P1ɺH�=ԙj1�jj�r���H�� ���B胱��H�3E1�E1�P1ɺH�=��j1�jj�>���H�� H�3E1�jE1�1ɺjH�=h�jjjj���B1��
���H��(H�3E1�jE1�1ɺjH�=/�j�{�B1��ܦ��H�� �n�B���H�3E1�E1�P1ɺH�=�j1�jj訦��H�� �>�B���H�����jH�3E1�UE1�1ɺPH�=�1�jjj�i���H��(H�3E1�jE1�1ɺjH�=ʱj��B1��;���H�� �وB�,��H���t���UH�3E1�jE1�1ɺPH�=��1�jjj���H��0���B荤��H�3E1�1�PL�̈́B�1�jH�=p�jj�ĥ��H��H�3E1�jE1�1ɺjH�=Z�j�M�B1�薥��H�� �@�B�׮��H�����UH�3E1�jE1�1ɺPH�=0�1�j�[���H�� �	�B蜮��H���d���UH�3E1�jE1�1ɺPH�=�1�j� ����ևBH��([]A\A]��H�5��BH���ٓ���O���@��H�E�BH��tH�9�B��H��H�=%�B萪����uH��BH���H�=���D���H��L�
����A� j��H�ƿPH�
�����X���ZH�=ІBYH���'���H���BH���ff.���A�ɉ�5نBA��1�1��U���D��H��ӥ����H������H�������H��s�����H��3�����H�鳺����H�������5R�B1�1��͠��ff.�f���H��53�B1�1�骠��f.���A��H��5�BI��1�1�鄠��@���5��B1�1��m���ff.�f���I��H��5܅BA��1�1��D���@��H��(dH�%(H�D$1��$H��D�D$H�D$���H�D$dH3%(uH��(�蕭��D��H��5C�B1�1��ڟ��f.���H��5'�B1�1�麟��f.���H��5�B1�1�隟��f.���H��I��H��5�BAP1�A��1��n���H���f���H��5ÄB1�1��J���f.���H��5��B1�1��*���f.���H��H��5��B1�dH�%(H�D$1�L�D$���D$H�T$dH3%(uH����u���D���5Z�B1�1�齞��ff.�f���H��H��57�B1�dH�%(H�D$1�I��腞��H�$H�T$dH3%(uH����������H��H��5�B1�dH�%(H�D$1�I���5���H�$H�T$dH3%(uH���跫�����SH�����1�H��1��NJ��H�X[Ð��SHc��BH�
-H�6BH�5�~BH�H�_H�=B�\���H�[����SH��H�?����H�;踸��H�{诸��H�{覸��H�{ �
���H�{(��H�{HH��t
H�CH�n��H�޿P[� ��SH��H��c���H�{�Z���H�޿([���ff.�����*L�Bu#SH��M��tH�J H�rH�z1�A��H��[��ff.�@��USH��H���N���H���B�y�B����H�߾P���H��脙��H��H���y��H��H�
;H�U0H�5�H��
H���H�
kH���H���H������H���)���jH�3E1�UE1�1�1�j@H�=�P1�j@jjj����H��@H�3E1�j@E1�1�1�jH�=��jj�B1��Ӟ�����BH��([]�fDH�5��BH��葍������ff.����AVI���(AUI��ATM��UH��S�>���L���H���}���H��H�C�q���L�kH�CL�c � ���H��H�����H��tHH���(���I��H��t`H��H��H��1�jL�]�1�H�=�VB詢��XZ[]A\A]A^�fDL��H�
����1�H�5��G����L��H�
b��1�H�5ݩ����ff.�@��ATH��USH��H�� H�jdH�%(H�D$1�H�t$H�D$H�D$�N���H�D$H��t-L�`���H��1���L��1��6���H�|$H�D$臍��H�K H�sH�T$H�{��H�|$�*���H������H�D$dH3%(u	H�� []A\�褧��@��AVI��AUM��ATI��UH���(S�~���H���H��轇��L��H�C豇��L�sH��L��L�k H��H�)H�C[]A\A]A^�H������H��(dH�%(H�D$1��D$��uH�D$dH3%(u5H��(�fD1�H�T$L�D$�߭����t�D$t�L�D$A������H��H��H�OH��P0�5�~BR1�p(L�H L�@1���H���ff.�AWAVAUATUSH��H��H�o�V���H������1�葝��L�k8L�{@I��L�s�}���H��H��肏��M��L��L��H��1�A��H�K�5C~BH��1�1��k���H�EH�sH�8�;���H��L��[]A\A]A^A_酋��D��AW1�AVAUATUSH��H�T$H�T$0dH�%(H��$�1�H�D$0H�D$8赒��H�D$苈��H�|$0����ڠ���D$����L�|$I�GH�@H�D$ H�D$0H����H�hH�\$8�0���H�
a�1�H��I���1����H�|$0辊��M�w@L�l$8M�gI�o8�H���I�H���L���M��L��1�L��H���H�D$H�pH�D$ H�8����H���.���H��$�dH3%(��H�Ę[]A\A]A^A_�DH��$�H�5,qH��H�D$(����H�D$H���I��H�\$PL�d$HH�l$@L�5��M�mM���|I�UH��H�$諊��I��H��t�H�$H���W���H��H��H�$�����fDH�|$@L���s�������L��H��H���=�����u�H�<$��L�����M�mM��u�H�5�wBH�|$�n���H�|$(�D����H�=B�H��H�D$L�@ L�������t�D$��u~H�D$�@0tsH�\$1�H��L��H�{腸��H��譈��H��H�CH����~���@1�L���Ϋ��H���։��H�T$HH�|$(H�5�oH��1�蛺���D$�%���fDH�=��H��$�t���H��H���y���L�t$H��H��H�5��1�I�V �L���H��I�n8M�f@�L���I�^I����I�~H����H��M��1�L��H��H�\$8�����f.�H�|$0�������H�5bvB1�����H�|$(����H�=�H��H�D$L�@ L����������������%���D��ATH��I��UH��H�5�SH��H��荤�����u'H�CH��tH�K 1�H��L���H��[]A\���@[]A\�ff.�AWAVI��1�AUI��ATI��UL��L��SH��H��H��8dH�%(H�D$(1�H�T$�D$H�D$ �p����|$t)H�D$(dH3%(�H��8[]A\A]A^A_�fDL���x���I��H���4M�}L�d$ H��LE�M���~L�$����L��H�����L�$H����H��L�D$H�$����H�$I��H���m���M��L�D$�/H����H���=M��t#L��H��H��1�H�=ĢL�$���L�$I�$jH�
ƢL��1�SH�5��L�
�H�=�NB�<���Y^H��H����L�d$ A�EH��M��H�w���AUH��H���ID�PE1�H�=<NBH���$���XH��Z����L�������DL�¡H�
���Z1�H�5E�臵���H�͡H�5R�1��;���L��H�
]��`1�H�5��J���f.�H�u�H�5�1�����f�H���H�5�1��ہ���f�H�'�H�5ң1�軁���{����1��������u�fDAUM��ATI��UH��SH��H��赌��H��tH�UH��tH;tbH��H���W�����uSH����H�;@t�@H��觖����tsH���[���H��tf�8taH��L��H��L��[E1�]H��A\A]����H�5�L������u��(���H��H��轇��H��L��H�5[H��]A\A]�A����H��[]A\A]�D��H��t�>uÐAVAUATI��UH��SH���ʋ��I�|$H���
���I��H��tu谋��L��H���E���H���m���I��H��t}I�|$����H����L��H��H��1�H�=B�����L��H��H��I��L��I�����[L��]A\A]A^鄟��@L���H�
B��w1�H�5ݞ�����L�֟H�
��y1�H�5������L�qH�
��{1�H�5���ϲ��ff.�@��H�uBH��tH�	uB��SH�=�tB����uH��tB[�fDH�=g�褚��H�����H��H��L�
�jA� ��H��H�
�����H��XZH���P���H��H�=�tB��tB�k���H�ttB[�f���ATI��UH��SH�����H��H��菅��H��L��H�=v�H�h1�臐��H�}H��H���X���H��H�����H��t[H��]A\�|���@[]A\���AWI��AVI��AUE��ATUH��SH��H��8H�D$pL�D$H�D$H�D$xH�D$dH�%(H�D$(1��Q���H��H�����H��H�=НL��I��1��ߏ��H��I�D$H��H�8謗��H��tH�����P腖��L��H���{��H�C�q���L��H��{��L��H�C蚍��H�|$H�C ����D�k0H��H��H�C(H�D$H�kH�C8H�D$H�C@I�D$H�8���A��umA����L�c���H��L���Ǵ��H����H�����L��H���{���H��H��tCL�l$$H��H�5d��D$$L���4����|$$����1����H��H�CH���zH�	�H�5ž1���|��L��谆��E1�M��H�
}�H��H�5��H�=BHB1����H��L�1�SH��H��L�
$�H�5HB�C���XH��Z���H�D$(dH3%(�H��8[]A\A]A^A_�fD諠��H�UH��tH;tH��H����������腜��H��L��芳��H��t&H�5N�L��H���D$$�����t$$������z���L��H���O���H�������L��H�5�H���D$$�ܴ���L$$�������������H�UH��tH9tH��H���j����������轘���S����L�<�H�
B���1�H�5-��o���躘��f.���AWAVAUATUH��SH��hdH�%(H�D$X1�L�d$0L�|$(�,���H��H�����H�XH�\$�S���H�ě���1�莏��H�3L��H�D$�>���H�D$ H�D$�6f�H�D$(L�p@H�XL�h8�Z���H��H���_���M��H�L$1�H��H��A��H�t$L��L���<�����u�H�D$H�8�K���H�|$�q}��H�=�oB�P����H��P0H�D$XdH3%(uH��h[]A\A]A^A_�薗��fD��AUI��ATI��UH��SH��H���!���H��t\H�H��tH9tH��H���Ğ����t@H�CH��H�8聓��H��tLH�xHH��L��1�[L��H��x]A\A]�
���f.�H�əH��H�5>�1�[]A\A]�qy���H�Ǚ�����AWAVA��AUATI��UH��SH��dH�%(H��$�1��Z���H���H�UH��tH9tH��H���������L�mL��I�}谒��H��H����A����A���PH�xH��u��I��C0��H�=�L�t$�z��L��H��讏��H�S L��L��H�5ԗ1�膭��L�C@L��L�{8L�D$�|��H�[I���%���H��H���*��H��L�D$1�H��L��A��I�}L�����H��$�dH3%(�'H�Ĩ[]A\A]A^A_�f�H�I�H�5��1��w���f�H�G�H�5��1���w���f�H�{�v��H�s 1�L��H��I���S����n���H��H���s~��1�1�L��H������L��茢���������~��H���1���1��)���L�C@L�{8I��H�[L�D$����H��H���~��H��L�D$L��1�H��A��L���Jz��I�}L���ޟ������f��{~��H�����x����AUH�5�iH�=c�ATUSH��(dH�%(H��$1�荔��H����H��L�d$L�l$H�-1��0fD1�L��H��H��������tSH�ھL�����H��tH�������t�H������H��$dH3%(u)H��([]A\A]�@H���ȏ���D$���˸��蔓��@��S1�H��1��_����ڃ�������C �Cǃ��C`[����SH���#����5ukBH�vkB���~H�߾P�a���H��H��H�C0H�|
H�C H�qH�C舐��A�H�	�H�5<qH��H�=:q�%���H�߾H������耤��A�H��H�58qH��H�=4q���H�߾H���ݗ��H��A�1�jA����H��H�5��H�=��迢��ZH��YH�¾蝗��A�1�H��H�5��H�=���ky��H�߾H���k���A�1�H�$�H�5k�H�=r��9y��H�߾H���9���A��H�Y�H�5o�H�=t���u��H�߾[H������H�5�iBH���u���n���ff.����SH��LJ�虢��H�{(H���z��H��襓��1�[Ð��SH��LJ��i���H�{(H���z��H���u���1�[Ð��H���c����H���f�SH��H�(L�
�E1�1�1ҾH��S�K���H�{(1�E1�L�
{1ɾH�$�+������XZ��u[�D�c���ǃ�[��AWAVAUATUSH��H���dH�%(H��$�1�H����GH���@����9Gv0H��$�dH3%(��H�Ę[]A\A]A^A_��H�z��譤��I��H��t�H���H�x����L��I���
���L��L)�I���w��H���H���t+M���ʚ;I)ŋ�����ݕ��I9��\���H�{(H�L$H�S4��L���L�D$������4����t$H�|$���J�K8�S4�CTD�KPD�CL�bu��I��L��E1�1��r���L��H�D$腛��L��A�������|$D��~!fDH�T$��J�<:I��賑��9l$�H�|$���~���H��D��M��5�bBH�t$I��1�1�H���J���Z�����YH��H���X����s`L�eH�����t~��H���H��H�z��w��H��H���F���H���.��������udH��H�5������r��H�5ٕ�������������H���H�5r�1��p������fDH�?��|��I������˪��ǃ����}��H�T$1�H�5��H��1��D$蓒���D$�������SX�C49������s\�K89�����C<9������K@9����H�{p��L�l$ H��L����n���S<�K@1�H�|$0D��蛓��H��I��� u���K\�SXf�+��f��+K8I��+���*�+S4H���*�H�sp詥��L���ў��L���ɗ��L��豏��L��H����v���H���H�{h�h���I��H��tzH�{hH���H�����r��L���a���L��A���6���F�4��ljD$A��Hc�謁��D��L��H��I������D�L$E��D��1�L��D��讒��L�kxH�CpH�{p����������a�������~ H��t�f�1����f���SH�H��t
�����t	[��H��H�5����<p��H�5�������[鷛���SH��H�H��t�s���{���t���H�{ 踍��H�;H��tH��4���H��[雍��ff.�SH��H��H�5�����1����H�5O������[�1������H�~pSH��H��t
���H�CpH�{xH��t
�4���H�Cx�{ t[�@�����u�H��[���AVAUATE1�UH��SH��H���dH�%(H�D$1�LJ�H��t8L�t$L�-^��H�1�L��L��H�x1��j���H�[Dd$H��u�D9etD�e1�H�T$dH3%(u
H��[]A\A]A^��~���ff.�UL���H���@SH�
�H�5
�H��j�GPPH��hjP�GLPH�OhjPj���H�=�PH�^BL�1�螆��H�}H��P1�H��H��H�5Ə1���H��H��[]邏��f�SH��1�H��H�hdH�%(H�D$1�H�T$H�������$�T$9CXt)�{ �CX�S\t.H�D$dH3%(u2H��[��9S\tރ{ �CX�S\uҋ����u�H�������V���fD��H���c����H���f���H�UaBH��tH�IaB��H��H�=5aB������uH�%aBH���H�=��Ć��H��L�
I���A��j��H�ƿPH�
m�����k��ZH�=�`BYH��觖��H��`BH���ff.���AUA��ATI��UH��SH��H���q���H��H���q��A����H�5�D��Hc�H�>����p`H��H��[]A\A]�{���H�x(�P�jq��H��H��[H��]A\A]����D���H��H��[]A\A]阊���H���H��H��[]A\A]�'����H������H�H�8�5t��H��I�$H�8�&t��SL�
W\1�PA�1�H�
P�A�t$H��]�AU�s��H��([]A\A]�f���SH���C���H��1�[H��H�5�e1��\f��ff.����U��SH��H������H��tGH�H��tH;tH��H���_�����t+;k`tA�k`H�߾P�&p��H��H�5v�[H��]�����H��H���H�5n�1�[]�%i��X[]Ð��AVA��AUA��ATE��U��SH��聅��H���H�H��tH;tH��H���Ѝ�������CD�C01�D9����SH�C41�9����S8A9�~[�{<A9�~C�C@H�{(H�KPH�SLH�s4L�CT�|��H���H����H�8[H�p]A\A]A^�s���E��AI��f�E��AI��{<A9����f.���H�Љ�)�S8A9��n������E���SHDH�D)��D��C41�9��>�����[H�L�]1�A\H�5ԐA]A^��g��[]A\A]A^�ff.�@��AWAVAUATI��USH��H��8dH�%(H�D$(1�����H����H�H��tH;tH��H���R�������H�{(���{ �eH�D$ �(�R���H��H���d��L���H�5���E����H�EH��M��LD�L���Hh��I��H���\�T�v���@��@�\1��L$�9l��L��L��L)�H��I���o���L$L��1�H�5�Y���Μ��I�vL���r���1�L���ȏ��I��1�H�L$ �L���A���L��H�E�Յ��H�}�����H�}H���m���H���<���I��H����1�H�=ˉ�R���H���I��H�EH����覆��H�}H����l��L��H���f��H�}H�u��1�H�=������H�m�I��H���H�\���H�}H���l��L��H���5f��H�}L��1�1��c��L��H�5<��F���I��H����L��H���i�����7L���j��L���j����H�}H���l���H���9���H�D$H����H�EH���H�D$H����1��{j��L�l$I��A�U����fD��%��A�M��%���s��d����t�i���H�5{�H��I���n���H����t��/u�:H�����u�H��L��H�L$�]���H�L$H�����L������I�E�PL�h���d���M�>L���e�����A��7~���H��H�D$�5���H�L$����L��1�H��1��n��I�Ǻ��AL��1��B���A�Ń�����!a���8���SL����	���L���1������@H��H�5��1��c��1�H�\$(dH3%(�TH��8[]A\A]A^A_�fD���LH����D���(H�y��1�1���l���E����H�|$��g����DI�FH�HI;N�vI�6I�N�I�FI��L�����L��H�=��1��8j���L��L�} ����1�H�=��D�m���I��H�����T���H�}H���xi��L��H���-c���UL��1�H�5�a1�����L��H�5Z��8���I��H����H�|$H���f�����gH�|$�f��L���f��H�}���g���g��H�}H���h��H���e��H��H�5�
I��H������L���f��H�}E1�E1�H��H�-�H�5��1z��H���H���H���f��H���M��tH���H�x �q��I�$H�{(E1�E1�H��H��H�5�f��y��H�{(E1�H��A�H���H�5�_�y���C H��Hǃ���������������;���H������H���{���H�s(1�H�=n��Yt�����讏��H�{(H����g��H��躀��H����^����G����H���H�5��1���`��1��#���@H�B�H�5��1��`��1�����@H�����L���!w��L���%���f�H�A�1��1��j��L���e��H�����1����@H����1�1���i����f�I�FH�PI;V��I�I�V�%I�I�F�����+���H�5��H��I���9j���H�����N�����/u�-H�����u��3���H��H�5��d�\a��H�5U�������،���S���L���xo��I�����H�D$ 1��H�}�H�H1��i��H�|$ �xb�����%H�����L����u�������H�)��1�1��h��L����c��L���c�����fDL����n��I�����H���1�1��{h��H�|$�c��L���yc���a���@H����1�1��Kh���j���fDH�	�����^��H���k����>w��L���1�I��H���1��h���L��薇��L���}��D�m�	����E��������{��@��SH���z��H����H�H��tH;tH��H���������C ��tc�������H���t#����H���H��H�z����Hǃ��C 謍��H���d|��H���,���H��[铈��H�i�H�5B�1�[�z]��f.�H�ՀH�5"�1�[�Z]��f.��{���ǃ��W���ff.����SH���y��H����H�H��tH;tH��H���������C ����H�߾����������H���t#����H���H��H�z����Hǃ��B���H�{(H���fc��H���N|�������ud�C �x���H���0{��H�����H��[�_����H�1�H�5�1�[�B\��f�H��H�5҄1�[�*\��f.��t��ǃ����3���ǃ�� ���@USH��(dH�%(H�D$1�H�G(H9���H���H��H��t	�(���H�C(H��tH����H�k(H��to�9���H��H���^b��H��H���3���f��f�K0�U�ZL$�Z$�X��\�f��ZD$�X��,�f���ZL$�\��,��SD�CH��tH�D$dH3%(u6H��([]���S<H�{(H�KPH�SLH�C4L�CT1��C@��n����dx��@��H��1����f�9��tHU��SH��H��H���t�������H�߾P�ka��H��H�5Z}[H��]�F��fD�ff.�@��U��SH��H����v��H��t/H�H��tH;tH��H���/����tH����H��[]�[���H��H��}H�5��1�[]�
Z��ff.�f�UH��SH��H��H���t�U���H���H��t�Dy��H���i��H�߾PH����`��H��H�5�|[H��]�c~��H���H9�tDUH��SH��H��H��tH��tH���ր����tH��H��H��[]�a����H��[]�f��ff.�@��UH��SH��H���u��H��t6H�H��tH;tH��H���}����tH��H��H��[]�Y���f�H��H�Q|H�5��1�[]��X��DUH��SH��H��H���t�%���H���H��t�x��H���h��H�߾PH����X_��H��H�5q{[H��]�3}��H���H9�tDUH��SH��H��H��tH��tH�������tH��H��H��[]�a����H��[]�f��ff.�@��AVAUA��ATI��UH��SH���st��H��H���^��A���&H�3�D��I��Hc�H�>���L����m��A;F`�DA�F`L���P�m^��[H�5�z]H��A\A]A^�F|��fDL����g��H����g��I;Fh��I�FhL��[E1�]H�c�A\H��A]H�5W{A^�.���fDL���g��[L��]H��A\A]A^�-���DL����Y��[L��]��A\A]A^�.���fDL���X��[L��]H��A\A]A^�=���DL����W��[L��]H��A\A]A^�M���DH�H�8�`��H��H�EH�8�`��SL�
�H1�PA��1�H�
�y�uH�jJ�AU�`��H�� []A\A]A^��UH��SH��H����r��H��t6H�H��tH;tH��H���{����tH��H��H��[]���f�H��H�qyH�5^1�[]�U��D��UH��SH���^r��H��H���\�����H��u~H�{pH��t��u��H�{xH��t�u��1�H�����H���t
1�H�����H���t
1�H����������u6H�='KB�P����H��H�@0H��[]��fD�s����x���fD�c���ǃ���AVL�
W�E1�1�AU�1�ATUSH��H�H��S��c��H�;XZ�Z�H�{��Z��H�+H��tYH9��tc�6m��H�{ I���:���H��I���o���L��H��I���1{��L���	t��L�����H���H���2t��H���H��[]A\A]A^��HDž�H���Ň�����SH��H���V@dH�%(H�D$1���tW���tH�L$dH3%(uIH��[�@H��1�H���[w��H�$1��H��{H�H1��~]��H�<$�V��H�����1���Dq��@��SH���3p��H��t.H�H��tH;tH��H���x����t1��{ [����H��vH�5�{1��kS��1�[����USH��H���G@H�0�]��H�{0H��t
H�C0�-~����j��H��1�H�<H�C0H��蠆��H�5����H��H���NX��H�5gH��迂��H��tH����}��H��1�[]���H���m���ff.�f���SH��H�H��t
H�C�2s��H�;H�5DB��o��H�޿[�5���DAWAVAUATUSH��H���	I��H���%I��1�1�L���u��I��H���2f�L���z��H��H����H��L���A�����uM1�H��L��1��\��I����z��1�1�L��H��H����U����u0H��tH���Ӏ��L���Kq���f�1��9q����E1�1�H��{H��H�5�{�`��H�D$H��t�H���a��H�T$H��L���Sb��두H��L��[]A\A]A^A_��e��f.�L�{H�
�|�g1�H�5{�o����L� {H�
r|�h1�H�5�z�G����H��[]A\A]A^A_���H��验����H��鞌�����Ì����H��餌����鷌����H���w���ff.�@AWAVAUATUSH��H����H����H��1�H�5�z1��n[��H��H��I�����N��H�8I��H��tA1�L�-fz�1�L��1��4[��H��H��I�����L���o���CI�<�H��H��u�H��L��[]A\A]A^A_�yo��f�L��yH�
J{��1�H�5�y������H���ċ����H���ԋ����H��鬋����麋����H��闋��fD��SH����~��H�lEB�bEB��uRH�߾P�5���H��H�3E1�H�TE1�1�H�P0H�=F�1�jjj�b��H�� �EB[�fDH�5EBH���P���ff.�@��SH��H�H��t
H�C��y���{@��u[H�{ H��t
H�C �e~��H�{(H��t
H�C(�Oo��H�{8H�5,@B��k��H�=�DB�P�^���H��[H�@0��@軈���C@�f���H�EDBH��tH�9DB��H��H�=%DB��f����uH�DBH���H�=\x�i��H��L�
	A�Hj��H�ƿPH�
m����N��ZH�=�CBYH���wy��H��CBH���ff.���AUI��ATUH��SH��H��dH�%(H�D$1�H�$�y��H����H�H��tH;tH��H���{r����t�z��I��H��tJH�EH��tL9 tL��H���Qr����t-M��txL��wH�
�x��1�H�5w�g����L�uwH�
�x��1�H�5�v�?����L�2wH�
�x��1�H�5�v�����L��H���S��H��H���zv��H���BM��H�<$����e����u`H�{8H�5>B��i��H�EH�EH�{(H�C8H��t
H�C(�m��H�E�5FBB1�H��H�EH�C(1��6\��H�����H�<$H��t�`O��H�D$dH3%(uH��[]A\A]��i����H���+���@��SH���Sx��H��t>H�H��tH;tH��H����p����t"�{@��uCH��H�5q������o���C@[�f�L�*vH�
�w��1�H�5zu��~����Å��붐��USH��H����H��H����w��H�UH��tH9tH��H���Ip�������w��H����H�H��tH;tH��H���p����t���c��H�V<BH�5<BH�=@<BH��H���}Z��H�C�Da��H�{H��8���H��H��H��[H����]��T��L�TuH�
�v��1�H�5�t��}���L�AuH�
Zv��1�H�5bt�}���AUATUH��SH��H���v��H����H�H��tH;tH��H���:o������1�H��t1�H�5AtH��1��U��H��I���y��1�1�1�H��I���q��H��H��t8��H���Wl��A�H��H��H����H�5�@�ȃ��H�{ H���<z��M��tL���t��L��H��[]A\A]�i��DL�tH�
u��1�H�5bs�|����鰅����H��鷅����H���˅����H��鿅����H��雅����H��鯅����H��雅����H��鏅��f.���ATUH��H�=�:BS��Z��H�E �`��H��H���u����F��H�0H��tI��1�H���X����CI�4�H��H��u�H�:BH�5�9BH�=�9BH���,X��H�E(H������z��H��A�H�{���H�EH�5=?H���h�����^��[H�E8]A\�f.���H�>BH��t��H���t��1�H��1���D���PH��H��=B��N��H�5�=BH����D��H��=BH���@��SH���Ct��H��t&H�H��tH;tH��H����l����t
H�C8[�@H��qH�5�r1��G��1�[�ff.�@��ATI��USH����s��H��thH�H��tH;tH��H���`l����tLH�[8H��u�V�H�[H��tLH�+H���Lo��L��H���1�����u���{��[H��]H��A\�M��f�H�JqH�5"r1��G��[1�]A\�@��UH��SH��H���;s��H��t>H�H��tH;tH��H���k����t"H��t2H�{(H���y`��H��[H��]�V��H��pH�5zq1��F��H��1�[]�f.���SH���W��H��H���8M���H���kE���V��H��H���M���H����j����D��H���H����&U��H����U��H���[�q��ff.�f���AUATUSH��H���Ju���5�;BH��;B����H�߾P�|��I��� G��H��H���u|��I���MV��H��H���b|��H���
G��H��H���O|��A�H�qH�5�qH��H�aH�=�hI�D$0H�^I�D$H�rI�D$ H��6BH��.g��L��H���g��H��E1�1�jA�����H��qH�5.qH�=i��h��Z�YH��L���g��H�=J7B�eP��L��H���m��L��L��qH�
�qH��pH�5�p�{��H��I��8H��H���H�#H��HH��H��PH�GH��H��H��H��H�� H��[]A\A]�f�H�51:BH����E���W���ff.��ATA��UH���SH��H���a�����D����t[H��]A\�e��f.�H��H�=&�[��H�5q������p��[H��]A\�e�����UH���SH��H��H���`��H��ǃ��Re��H�߾P�EJ��H�5tgH���&h��H��1�[]�ff.�f����gf�����H�%9BH��tH�9B��SH�=9B�[����uH��8B[�fDH�=0o�T^��H���\D��H��H��L�
^���jA���XH��H�
����bC��ZH�=�8BYH���1n��H��8B[����AWI��AVAUATUSH��H����O��H��H���HI��L���H��L��L����o_��D���E��t"�q������uvL��L����z�������uQH�����j��H��H��t�L����c��H���I��H��H��
H�������I�/H��D��[]A\A]A^A_��L��A������c����L��A������c������USH��H����N��H��H���sH��H���H��H���^��H���1�H�H���H�5 ����y��H����/g��H�����?��H���c��H���[]�ff.�@��USH��H���^N��H��H����G��H���H��H���^��H���Hǃ��j?��H���b��H���[]�fD��USH��H���M��H��H���G��H���H��H���]��H���ǃ��?��H���Sb��H���[]����USH��H���M��H��H���#G��H���H��H���Q]��H���ǃ��>��H����a��H���[]����SH���CM��H��H����F��H��[H�����o�����AVI��AUI��ATUH��S���M��H��H���F��I�ă�ta��t|H�EH�8�I��H��I�EH�8�I��UL�
�11�PA�'1�H�
�kA�uH�\3�S�I��H�� []A\A]A^�@H���[L��]A\A]A^�	q��f�H���H���1\��A��$�L���K��H��[]A\A]A^��`�����AVI��AUATI��UH��S���$L��H��H���E����t\H�EH�8��H��H��I�$H�8��H��UL�
�01�PA�1�H�
�jA�t$H��2�S�(H��H�� []A\A]A^�I��L����K��I���H��H9�t�H��t�ta��IDž�H��tH���I��I���[]A\A]A^�@IDž�[]A\A]A^�@��UH��SH���>K��H��H����D�����H�Å�uvH���H��t�`��Hǃ�H���H�5l����7q��H����>��H����=��H����<��H�=�3B�P�Bt��H��H�@0H��[]���w���f���SH���J��H��1�H�5�`H���_a���[����ATUSH����H��H���TJ��H�H��tH9tH��H����a����thH���t~H��L�����C��H��H��
H�����L����Y��H����u��H���H����E��H����%;��[L��]A\�i^��f�H��h[H�51j]1�A\�w<���H��h�����UH���SH��H��H���TY��H���ǃ��:��H��H��[]��]����UH��SH��H���KI��H��H����B���{@ntW�R=��H�=�1BH���r��H��8t)�4=��H�=�1BH���r��H��H��H��8H��[]��H��1�[]��H���v��H���^��H���[]�@���^1B��tÐH��H��hL�
/+�PH��5L����PH�
�gH��gPH��gP�h9���1BH��(�f����ff.����ff.���SH���Cj��H��0B��0B��uJH�[H�C0H�����H��H�bH���H��H���H�fH�� [�DH�5�0BH���<������SH��H�=y0B�P�q��H��[H�@0��ff.�f�USH��1�H���?���3H��@��tDH��H����E���3@��u�H��H��1�[]�<c��ff.����H��/BH��tH��/B��SH�=�/B�SR����uH��/B[�fDH�=wg�U��H���H��H��H��L�
����jA�@�H��H�
�����:��ZH�=r/BYH����d��H�b/B[����AUATUH��SH���:8��H��H���?��H��H����S��I���|>��H�=-/BH��I����o��H���H���@��H��C8�|f��f��f��f��J��H��I��H���@��L���B���c8�L��H��1�C8@���x?����H���np���k8L��H���_?��H��@��[H�ǃ���]A\A]�ee��DH����^��H���G��I9�t�c8��D1�H���@���K8���AUATUSH��dH�%(H��$�1�H��t'H��H���[��H�H��tH9tWH��H����\����uHH��eH�5�fH�=�e��7��1�H��$�dH3%(�\H�Ĩ[]A\A]��H���H����A��H���o��H��I���>��H��I���	5��L��H�T$H���ik��L��A���d��E����H�D$(H��H�p�tV��H��H�����36��1�H��1���3��H�h0I���{<��L��H��H����=��H��H���b@��H�L$1�1�H���Am��H�L$1�1�H��� ^��1�1�H�L$H���B��H���K���T$T$H��T$L��9�A�D$8�ƒ�	�A�D$8�k=������fDH��dH�52eH�=ld�v6��1�����1������S��ff.����AUATUH��SH��8dH�%(H�D$(1��*5��H��tH�UH��tH9tWH��H���Z����uHH�dH�5�d1�H�=�c��5��H�L$(dH3%(H���6H��8[]A\A]�f.��;��H��H���<��H����A��H�5�cH��H���VK��H�5�cH��I���DK��H��I���	3��H��H�m0�m��H�T$ E1�1�RH��H��I��������H�T$ RH�T$ RH�T$RH�T$(RL��AT�8��H��0H��1ۉ���e��	��,���H�\$ L9d$uD�|$u=H�t$H��t31�H���Ql����t=H�|$ H�t$�8��H�|$ H���W�����@H������H��1��W������H�|$ 1��W������R��f.���SH���3��H��t.H�H��tH9tH��H���VY����t�C8[���f�H�`bH�5�bH�=,b�64��1�[�f���AWAVAUATI��UH��SH��8dH�%(H�D$(1��3��L��H����:��H��H���}A�������C8u/1�H�T$(dH3%(��H��8[]A\A]A^A_�f.�L���H;��H��I���n��H�t$H��H����5��H���e��D�L$L��D�|$D�t$D�l$D�L$��2��L��H���iF��H���qk��H��E��D��jD�L$D��H��H���T��XZH��D�D$�L$�T$�t$�Y���3���f�f��f��f��H��f��(A��H����:��H���b������YP��f���AUATUSH��H���dH�%(H��$�1��8��H��H���i9��H��H���N=����t�C8u,H��$�dH3%(��H���[]A\A]��H���>��H��I���ej��I���A��H��H���9��H���L��H��H��H����I��H���D$�|1��L���D$HH�D$0H�D$H�D$8H�D$@�t/��H�t$0L��1�L�D$���{4��L����^���:����9O��f���AVAUATA�UH��SH��H�� dH�%(H�D$1�I���|0��H��H���A8��L��H��I���3I���$9EuE1�D$9EA�ċD$9EtdH���;�������6��H�=@'BH����g��H��H���H����;������H�D$dH3%(��H�� []A\A]A^��D$9Eu�E��u��-6��H�=�&BH���~g��H��H�����L���8>�����l���H���88��H���W��1�L��H���c\���J���fD�s/��H��H���87��H����=����u$E���O���A�F8�D���L���@Z���7���H����7��H���V��1�L��H���[�������M�����AVAUATI��UH��SH��H�� dH�%(H�D$1���.��H����H�H��tH9tH��H���T�������5��H��H���y6��H����;��L�s0H���g��H�$H�D$H��I���	6��H��H���,��L��H��L���;��H���\��H��tH�EH�<$M���~I�$H��t���H�<$I�$�Q��H�|$H��tH��t���H�|$H�E��Q��H�D$dH3%(u4H�� []A\A]A^�H��\H�5"]H�=�\��.����@H��u���4L��@��S1�H��H�G01��i�������������H�C`1�f�CD�CFf�SH�CJf�KL�CNf�sP�CR[���USH��H���]��H�w$B�i$B���iH�"H�C0H�wH�CH�\H�C �sV��H��\A��E1�H��H��H����4��H�߾H���P���@=��H�3E1�E1�PH�Ź��jH�=�\1�jj�u@��H�� H�3E1�UE1����jH�=r\jj��#B1��C@��H��H�3E1�j E1����j H�=Q\j@Ujjj��#B1��@��H��8H�3E1�j E1����UH�='\jjj�f#B1���?��H��(H�3E1�jE1����jH�=\j�9#B1��?���0#BH��([]�f�H�5�"BH���a.�����ff.��AUATUSH��H�_0H���iH��H���.4��I���VC��H�H��tH9tH��H���^Q������H���V������M�����hR��I�$H��tH9tH��L���Q������H���O8��H�uH��I���P@��I9���H��H�5=L���,��H�E0H���F��H�߾P�2��H��[H��]A\A]��V��fDL�9\H�
�]�H�5�ZH�=�Y��^��f.�L��ZH�
�]�H�5�ZH�=`Y�^��f.�L��ZH�
�]�H�5�ZH�=0Y�j^��f.�H��[]A\A]�DL���H`��H�U1�L��A�����)�����ff.�f���UH��SH��H����)��H��H���1��H�{`H�p0H���S���5� BH��1�H��1��:��H��1�[]�f�AUATUSH��H��H�0dH�%(H�D$1�H�����2��H��H����H�{0�6��H�5�YH��I���
@���S@H��I��1�����H�$�)��L��H���Fb��j�H��E1�A� H��H�T$RL���B?��Y^H�D$dH3%(uLH��[]A\A]�DH�IYH�5r[H�=�W��)����@H�CH�5R[H�=�W�)����G��ff.�@AUATUSH��H��xH�0dH�%(H�D$h1�H���^�1��I��H���mH�{0�|5��H�5�XH��I���>���SDL��H�ʼn����SEH�H�$�����SFH�H�D$�����SHH�H�D$�����SIH�H�D$�����SJH�H�D$ �����SLH�H�D$(�����SMH�H�D$0�����SNH�H�D$8�����SPH�H�D$@�����SQH�H�D$H�����SRH�H�D$P����H�H�D$X��'��L��H���m`��jE1�A� �H��H��H�T$RH���i=��XZH�D$hdH3%(uKH��x[]A\A]�@H�qWH�5YH�=�U��'����@H�)AH�5�XH�=�U��'����GE�����AWAVAUATUH��SH�����!t1ۃ�tmH����[]A\A]A^A_�@I��H�G(H;B tk1�H;E(u�H�]XH��t0L�+I�D$ I9E(u�	�L�+I9E(��H�[H��u���5zBH��1�1��6��H���f����t����H�W@H����H���NH���t���H�]XH�FHH�v H��u�J@H�[H��t=L�+I9u(u�I;Eu�I�} �'F��L���F��H�}XH���'��H��H�EX�';��I�t$ H�}`�I@��H���F���M�D$H�5�BH��1�H��1���A6�����@H�_HH�}`H���@��H������H�}8H���$��I��H������5aBH��1�H��1���5���m+��L��H����,��H���jP��I���R_��M���ZI�UH��tH;tH��L���J�����9E1�E1�H��L��H����H�5nU�=���5��L��H��I���f,��H��H���{'��L��L���P,��H���@��H����H�}`H��L���+6��L���CP�����I�EA�I�}I�t$8H��LN�H)�I} Mc�L���Q9��M)u����I�u(H�}`��>��H��t'H��M�E A�uH��M�M1�1�H��5-B�4��XZI�} �ED��L���=D��H�}XH���%��H�߻H�EX�@9������H�v H�}`�^>��H��H���X���H�]XI�T$HM�|$PM�t$XH��t]I�D$ �H�[H��tJL�+I;E(u�M;uu�I�} H�T$H�$�C��L���C��H�}XH���2%��H��H�EX�8��H�$H�T$M��u-H���5VBM��H��RL��31�1����3��Y^�N����0H�$�S��H�$I�H��I�D$ H�SH�C(L�{L�3L�{�56��H�SH�}XH��H�C ���A��H�EX��L���=���H����5�BL��1�H���J3������D��H�uBH��tH�iB��H��H�=UB�;����uH�EBH���H�=�R�t>��H��L�
Y�A�hj��H�ƿPH�
���#��ZH�=BYH���WN��H��BH���ff.���AUI��ATI��UH��S��H���0��H��H���g)����tbH�EH�8�,��H��I�$H�8�,��UL�
�A��PH�
�Q1�H�HA�t$�H�=8PS��+��H��([]A\A]�fD�p@H��L��[]A\A]��B��ff.���UH��SH���/��H��H����(��H��H���8���H�{X�0��H�{`��Q��H�=�B�P�X��H��H�@0H��[]��fD��H���/��1�H��H��1��C����AWAVAUATUSH��dH�%(H�D$x1�H�8tDH�8QH�5fSH�=@O�J!��1�H�L$xdH3%(�vH�Ĉ[]A\A]A^A_�f�H���/��H�H��tH;t;H��H����E�����D$u(H��PH�5�RH�=�N�� ���D$��H�{8t)H��PH�5�RH�=�N� ��1��`�����[M��H�C8H��H���*��H��I���q3��H��I������H��H���)����H���;����<��H�=bP��1��)2��1�H��I����T��L��H�C�?��H�k0�PH����&��H������H���o���H�{0H�����'��I��H���H�{0�,��H�5PH��I���5��H�{8H�D$�(Z��H�{8H�����Z��H����.��H���fN��L��H�D$�	��L��I���W��jE1�A� � L��H��H�T$RH�T$�4��XZH������H���&��H��I���{T��H�SL��L��A���A���!������I�GH�5[OL���D$!H�D$0��4��H�sL���D$@ H�D$8D��H�D$H�(R��L��H�D$P�K��I�oL��H�D$`H�D$XH�D$h��V��L�D$1�H��H���Q!��1�H�=�N�CS��L��H����Q��1�H�=�NH�C �&S��L��H���Q��H��H�5����L��H�C(�u �������H��D$�\8��H���4I��H�C0�D$H�C8����H��MH�5jOH�=�K������f�H�17H�5BOH�=�K�����k�����&��H���-�������9;��f���ATUS�cJ��H����'��H���#:��H�=�M��1��c/��H��H��H���53��H��I����<��H���U��L��H���7U��[]H��A\�����f���U��SH��H���,+��H��t'H�H��tH9tH��H���A����t9k@u&X[]�f�H��H��LH�5�M[H�=�J]�����k@H���5�H�߾P�#��H��H�5K[H��]�sA����AVI��AUATI��UH��S���*��H��H���Y#����tdH�EH�8�&��H��I�$H�8�y&��UL�
�A��PH�
�K1�H�:A�t$�H�=*JS��%��H�� []A\A]A^�fDL��I���e��[L��]��A\A]A^��9����AVM��AUI��ATI��UH��SH����)��H��t[H�H��tH9tH��H���@����t?H�{DH����L����uW�EH�߉CDA�$�CHA�E�CLA��CP[]A\A]A^��D[H�kKH�5QL]H�=ZIA\A]A^�^��fDH�{HL���lL����t�H�{LL���\L����t�H�{PL���LL�����u���[]A\A]A^�ff.���SH���)��H��t&H�H��tH9tH��H����?����t
�C@[�DH��JH�5�KH�=�H���1�[���H��H���ShellActionModeShellAppStateShellSnippetHookShellNetworkAgentResponseSHELL_NETWORK_AGENT_CONFIRMEDconfirmeduser-canceledinternal-errorSHELL_SNIPPET_HOOK_VERTEXvertexvertex-transformSHELL_SNIPPET_HOOK_FRAGMENTtexture-coord-transformlayer-fragmenttexture-lookupSHELL_APP_STATE_STOPPEDstoppedSHELL_APP_STATE_STARTINGstartingSHELL_APP_STATE_RUNNINGrunningSHELL_ACTION_MODE_NONEnoneSHELL_ACTION_MODE_NORMALnormalSHELL_ACTION_MODE_OVERVIEWoverviewSHELL_ACTION_MODE_LOCK_SCREENunlock-screenlogin-screensystem-modallooking-glassSHELL_ACTION_MODE_POPUPpopupSHELL_ACTION_MODE_ALLallSHELL_NETWORK_AGENT_USER_CANCELEDSHELL_NETWORK_AGENT_INTERNAL_ERRORSHELL_SNIPPET_HOOK_VERTEX_TRANSFORMSHELL_SNIPPET_HOOK_TEXTURE_COORD_TRANSFORMSHELL_SNIPPET_HOOK_LAYER_FRAGMENTSHELL_SNIPPET_HOOK_TEXTURE_LOOKUPSHELL_ACTION_MODE_UNLOCK_SCREENSHELL_ACTION_MODE_LOGIN_SCREENSHELL_ACTION_MODE_SYSTEM_MODALSHELL_ACTION_MODE_LOOKING_GLASShandle-activatehandle-openhandle-command-lineBusyborg.gtk.Application(ssv)src/org-gtk-application.cShellOrgGtkApplication(@a{sv})Activate()(^ass@a{sv})Open(o^aay@a{sv})CommandLine(i)ShellOrgGtkApplicationProxya{sv}{&sv}g-flagsg-interface-nameg-object-pathg-connectiong-bus-typeNo property with name %sas(sa{sv}as)PropertiesChangedorg-gtk-applicationexit_statusiplatform_dataargumentsaayhinturisorg.freedesktop.DBus.Properties.Setprop_id != 0 && prop_id - 1 < 1Error setting property '%s' on interface org.gtk.Application: %s (%s, %d)G_VALUE_TYPE (a) == G_VALUE_TYPE (b)_g_value_equal() does not handle type %sShellOrgGtkApplicationSkeletonMethod %s is not implemented on interface %sorg.freedesktop.DBus.Properties[generated] _shell_org_gtk_application_emit_changed�V��X��X��X���W��X��X��X���W��X��X��X���W��X��X��X��X��X��X��X��X��X��X��X��W��X��X��X��0W��X��X��X��X��X��X��X��X��X��X��X��X��X��X��X��PW��X��X��X���W��X��X��X��X��X��X��X��X��X��X��X��X��X��X��X��X��X��X��X��pV��shell_org_gtk_application_skeleton_get_propertyshell_org_gtk_application_skeleton_set_property_g_value_equal_shell_org_gtk_application_skeleton_handle_method_call_shell_org_gtk_application_skeleton_handle_get_property_shell_org_gtk_application_skeleton_handle_set_propertyshell_org_gtk_application_proxy_get_propertyshell_org_gtk_application_proxy_set_propertywindow-managerGnomeShellPluginglXQueryExtensionsStringglXQueryExtensionGLX_INTEL_swap_eventglx.swapComplete<main>GNOME Shell0.1VariousGPLv2+GL buffer swap complete event received (with timestamp of completion)imports.ui.environment.init();imports.ui.main.start();Execution of main.js threw exception: %sProvides GNOME Shell core functionalityapp->info == NULL../src/shell-app.capp->running_state->windowsapp->running_state != NULLShellAppApplication stateBusy stateApplication idApplication Action Groupaction-groupDesktopAppInfoapp-infoSHELL_IS_APP (app)notify::busyscale-factoropacityfallback-app-iconapplication-x-executableprogramUnknownactionswinapp->running_state->muxerapp.new-windowX-GNOME-SingleWindowpropertystate->refcount > 0app.quitDRI_PRIMEFailed to launch “%s”app->info != NULLworkspace-switchedapp->running_state == NULLunmanagednotify::user-timenotify::skip-taskbarwindow:%d!(app->state == SHELL_APP_STATE_RUNNING && state == SHELL_APP_STATE_STARTING)The desktop file id of this ShellAppThe action group exported by the remote applicationThe DesktopAppInfo associated with this app%s:%d: invalid %s id %u for "%s" of type '%s' in '%s'app->state == SHELL_APP_STATE_STOPPEDapp->running_state->session != NULL0����������؊��0�����������unref_running_state_shell_app_remove_windowshell_app_state_transitionshell_app_sync_running_statebusy_changed_cbget_application_proxyshell_app_on_ws_switchcreate_running_stateshell_app_on_user_time_changedshell_app_on_skip_taskbar_changedshell_app_open_new_windowshell_app_activate_fullshell_app_update_window_actionswindow_backed_app_get_windowshell_app_disposeShellAppSystemapp-state-changedinstalled-changedgnome-.desktop../src/shell-app-system.cfedora-mozilla-debian-_shell_app_system_notify_app_state_changed[gnome-shell] idle_save_application_usageCould not load applications usage data: %s/org/gnome/SessionManager/PresenceMissing attribute id on <%s> elementCould not save applications usage data: %s<?xml version="1.0"?>
<application-state>
notify::focus-apporg.gnome.SessionManagerg-signaluserdatadirapplication_stateorg.gnome.desktop.privacychanged::remember-app-usage %s="application-statecontextscorelast-seenUnknown element <%s>ShellAppUsageStatusChanged(u)  <context id="">
    <application/>
  </context>
</application-state>
��@�?�@resize-modeapp-paintableShellEmbeddedWindowSHELL_IS_EMBEDDED_WINDOW (window)_shell_embedded_window_unmap_shell_embedded_window_map_shell_embedded_window_allocate_shell_embedded_window_set_actor/usr/share/gnome-shellGNOME_SHELL_DATADIRGNOME_SHELL_JSimages/%s/LEorg.gnome.shell:resourceresource:///org/gnome/shellsearch-pathnotify-erroruserThe session mode to useSession Modesession-modeScreen width, in pixelsScreen Widthscreen-widthScreen height, in pixelsScreen Heightscreen-heightDisplaydisplayWorkspace managerworkspace-managerStagestageActor holding window actorsTop Window Grouptop-window-groupWindow management interfaceWindow ManagerSettingssettingsData directoryImage directoryimagedirUser data directoryThe shell's StFocusManagerFocus managerfocus-managerFrame Timestampsframe-timestampsFrame Finish Timestampsframe-finish-timestampG_IS_FILE (path)../src/shell-global.cbytes != NULLglFinishShellGlobalclutter.stagePaintDoneclutter.stagePaintStartthe_object == NULLSHELL_IS_GLOBAL (global)global->plugin == NULLnotify::widthnotify::heightafter-paintStart of stage page repaintPaint completion on GPUnotify::key-focusnotify::focus-windowui-scaling-factor-changedMESSAGE=CODE_LINE=1205i == n_opts-1XdndAwareXdndProxy/proc/self/cmdline/proc/self/fdfailed to reexec: %sglobal->work_count > 0%s/gnome-shell/runtime-state-%s.%sMetacity display object for the shellStage holding the desktop scene graphActor holding override-redirect windowsGSettings instance for gnome-shell configurationDirectory containing gnome-shell data filesDirectory containing gnome-shell image filesDirectory containing gnome-shell user dataWhether to log frame timestamps in the performance logWhether at the end of a frame to call glFinish and log paintCompletedTimestampCould not delete runtime/persistent state file: %s
!cancellable || G_IS_CANCELLABLE (cancellable)Failed to open runtime state: %s[gnome-shell] run_leisure_functionsCould not replace runtime/persistent state file: %s
failed to resolve required GL symbol "%s"
clutter.paintCompletedTimestampEnd of frame, possibly including swap timeCODE_FILE=../src/shell-global.cfailed to get /proc/self/cmdline: %s���P��h�������������������0��H��`��x����������� ��replace_contents_asyncshell_global_get_session_modeshell_global_end_workshell_global_log_structured_shell_global_set_pluginshell_global_get_window_actorsshell_global_set_stage_input_region_shell_global_initShellGLSLQuadklass->base_pipeline != NULLRGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))shell_glsl_quad_add_glsl_snippetShellEmbeddedWindow to embedwindow-createddestroymapShellGtkEmbed../src/shell-gtk-embed.cshell_gtk_embed_newcogl_texel = texture2D (cogl_sampler, cogl_tex_coord.st);
vec3 effect = vec3 (cogl_texel);

float maxColor = max (cogl_texel.r, max (cogl_texel.g, cogl_texel.b));
float minColor = min (cogl_texel.r, min (cogl_texel.g, cogl_texel.b));
float lightness = (maxColor + minColor) / 2.0;

float delta = (1.0 - lightness) - lightness;
effect.rgb = (effect.rgb + delta);

cogl_texel = vec4 (effect, cogl_texel.a);
Unable to use the ShellInvertLightnessEffect: the graphics hardware or the current GL driver does not implement support for the GLSL shading language.ShellInvertLightnessEffectmessagedescriptionwarningpassword-newpassword-strengthchoice-labelchoice-chosencaller-windowcontinue-labelcancel-labelPassword field is visiblePassword visiblepassword-visibleConfirm field is visibleConfirm visibleconfirm-visibleWarning is visibleWarning visiblewarning-visibleChoice is visibleChoice visiblechoice-visibleText field for passwordPassword actorpassword-actorConfirm actorconfirm-actorshow-passwordshow-confirmvalue != NULLG_VALUE_HOLDS_STRING (value)stripped_label != NULL../src/shell-keyring-prompt.cShellKeyringPrompttext-changedself->mode != PROMPTING_NONEself->task != NULLPasswords do not match.GNOME_KEYRING_PARANOIDPassword cannot be blankself->task == NULLText field for confirming passwordg_task_get_source_object (task) == promptg_async_result_is_tagged (result, shell_keyring_prompt_confirm_async)g_task_get_source_object (G_TASK (result)) == promptg_async_result_is_tagged (result, shell_keyring_prompt_password_async)this prompt is already promptingthis prompt can only show one prompt at a timeSHELL_IS_KEYRING_PROMPT (self)password_actor == NULL || CLUTTER_IS_TEXT (password_actor)confirm_actor == NULL || CLUTTER_IS_TEXT (confirm_actor)��������� �8�@�X�h�x���������������x�������0�p������������������8�X�shell_keyring_prompt_cancelshell_keyring_prompt_completeshell_keyring_prompt_set_confirm_actorshell_keyring_prompt_set_password_actorshell_keyring_prompt_get_confirm_actorshell_keyring_prompt_get_password_actorremove_mnemonicsshell_keyring_prompt_disposeshell_keyring_prompt_password_finishshell_keyring_prompt_confirm_finish�?�?$@show-processes-2ShellMountOperationEvent names can't include '"'perf.setTimeUnknown statistic '%s'
\",
  [%li, "%s"][%li, "%s", %i][%li, "%s", %li][%li, "%s", "%s"]../src/shell-perf-log.cShellPerfLogperf.statisticsCollected[ ,
    "statistic": true } ]Only supported event signatures are '', 's', 'i', and 'x'
Maximum number of events defined
Duplicate event event for '%s'
Discarding unknown event '%s'
Event '%s'; defined with signature '%s', used with '%s'
Discarding oversize event '%s'
Statistic '%s'; defined with signature '%s', used with '%s'
[gnome-shell] statistics_timeoutFinished collecting statisticsperf_log->events->len == EVENT_SET_TIME + 1perf_log->events->len == EVENT_STATISTICS_COLLECTED + 1Only supported statistic signatures are 'i' and 'x'
Unsupported signature in event{ "name": "%s",
    "description": "%s"replay_to_jsonshell_perf_log_initinitiatecancel[gnome-shell] handle_cancelled_in_idleInvalid UTF-8 in username for uid %d. SkippingError looking up user name for uid %dUnsupporting identity of GType %sAuthentication dialog was dismissed by the userShellPolkitAuthenticationAgentPolKit failed to properly get our sessionSHELL_IS_POLKIT_AUTHENTICATION_AGENT (agent)agent->current_request != NULLshell_polkit_authentication_agent_completeOnly one screenshot operation at a time is permittedg_async_result_is_tagged (result, shell_screenshot_screenshot)g_async_result_is_tagged (result, shell_screenshot_screenshot_area)g_async_result_is_tagged (result, shell_screenshot_screenshot_window)g_async_result_is_tagged (result, shell_screenshot_pick_color)cairo_image_surface_get_format (priv->image) == CAIRO_FORMAT_ARGB32actors-paintedmagnifier-activeShellScreenshotscreenshot != NULL../src/shell-screenshot.c%s.png%s - %d.png%c%FT%T%ztEXt::SoftwaretEXt::Creation Timegnome-screenshotshell_screenshot_pick_color_finishshell_screenshot_screenshot_window_finishshell_screenshot_screenshot_area_finishshell_screenshot_screenshot_finishwrite_screenshot_threadShellSecureTextBufferShellStackThe PID of the icon's applicationevent_type == CLUTTER_BUTTON_RELEASE || event_type == CLUTTER_KEY_PRESS || event_type == CLUTTER_KEY_RELEASEshell tray: plug window is gonepidThe icon's window titleTitleThe icon's window WM_CLASSWM ClassShellTrayIcon_NET_WM_PIDwindow != NULL../src/shell-tray-icon.cshell_tray_icon_clickshell_tray_icon_newshell_tray_icon_constructedtray-icon-addedtray-icon-removedrealizeplug-addedBG Colorbg-colorchild != NULLShellTrayManager../src/shell-tray-manager.cstyle-changedBackground color (only if we don't have transparency)na_tray_icon_removed�o@G_IS_FILE (file)fd %d is not CLOEXECshell-stop-pickCLUTTER_IS_ACTOR (actor)LC_TIMEG_IS_TASK (res)drawn_captures > 0../src/shell-util.ctarget_scale > 0.0fUnknown value of _NL_TIME_WEEK_1STDAY.
File %s contains invalid UTF-8Actor resource scale is not know at this point, falling back to default 1.0Open fd CLOEXEC check completeshell_util_composite_capture_imagesshell_util_touch_file_finishshell_util_touch_file_asyncshell_util_get_transformed_allocation�?K���ShellWindowTrackerwindow-addedwindow-removed../src/shell-window-tracker.cFocused applicationFocus Appstartup-sequence-changedtracked-windows-changednotify::wm-classnotify::gtk-application-idnotify::n-workspacesget_app_from_idunminimizesize-changedsize-changekill-switch-workspacekill-window-effectsshow-tile-previewhide-tile-previewshow-window-menufilter-keybindingconfirm-display-changecreate-close-dialogShellWMcreate-inhibit-shortcuts-dialognew-requestcancel-requests_con../src/shell-network-agent.cconnection-uuidCanceled by NetworkManagersetting-keya{sa{sv}}{s@a{sv}}setting_names_con != NULLconnection_uuid != NULLconnection_id != NULLsetting_key != NULLNetwork secret for %s/%s/%ssetting-nameattrsdefaultsecretssettingservice_nameVPN %s secret for %s/%s/vpnShellNetworkAgentsetting != NULLSHELL_IS_NETWORK_AGENT (self)request != NULLThe request could not be completed.  Keyring result: %sInternal error while retrieving secrets from the keyring (%s)The secret agent is going awayNetwork dialog was canceled by the userAn internal error occurred while processing the request.org.freedesktop.NetworkManager.Connectionshell_network_agent_respondshell_network_agent_set_passwordis_connection_always_askcreate_keyring_add_attr_listsave_one_secretvpn_secret_iter_cbshell_network_agent_delete_secrets/proc/meminfoMemTotal: %uDisplay to recordStage to recordFramerateframeratePipelinepipelineFile Templatefile-templateWhether to record the cursorDraw Cursordraw-cursorBGRxformatvideo/x-rawcaps../src/shell-recorder.cSHELL_IS_RECORDER (recorder)recorder->stage != NULL%Tshellrecordersrcvideoconvert%0x%0XRecording to %s
fdsinkCan't create fdsink elementnotify::memory-usedcursor-changedFramerate used for resulting video in frames-per-secondGStreamer pipeline description to encode recordingsThe filename template to use for output filesrecorder->current_pipeline != NULL[gnome-shell] recorder_redraw_timeout[gnome-shell] recorder_update_memory_used_timeout[gnome-shell] recorder_idle_redrawvp9enc min_quantizer=13 max_quantizer=13 cpu-used=5 deadline=1000000 threads=%T ! queue ! webmmuxrecorder->state != RECORDER_STATE_RECORDINGShellRecorder: failed to parse pipeline: %sShellRecorder: pipeline has no unlinked sink padCan't create recorder source elementCan't create videoconvert elementShellRecorder: can't get src pad to link into pipelineShellRecorder: can't link to sink padUnknown escape %%%c in filenameCannot open output file '%s': %sShellRecorder: can't get sink pad to link pipeline output[gnome-shell] recorder_update_pointer_timeoutrecorder->state != RECORDER_STATE_CLOSEDError in recording pipeline: %s
�m���m��m��@m��`m���m��m����$���l�����������̀�����shell_recorder_is_recordingshell_recorder_closeshell_recorder_close_nowshell_recorder_recordshell_recorder_set_areashell_recorder_set_pipelineshell_recorder_set_draw_cursorshell_recorder_set_file_templateshell_recorder_set_frameraterecorder_record_framefolders != NULL../src/shell-app-cache.cpath != NULLNameDesktop Entrydesktop-directoriesShellAppCacheSHELL_IS_APP_CACHE (cache)G_IS_TASK (result)user_data == NULLSHELL_IS_APP_CACHE (self)G_IS_TASK (task)SHELL_IS_APP_CACHE (source_object)shell_app_cache_translate_foldershell_app_cache_get_infoshell_app_cache_get_allload_folderload_foldersmonitor_desktop_directories_for_data_dirapply_update_cbshell_app_cache_workershell_app_cache_queue_updateFixed GstCaps for the sourceCapsMemory UsedGeneric/SrcShellRecorderSrc../src/shell-recorder-src.cSHELL_IS_RECORDER_SRC (src)src->caps != NULLPlugin for ShellRecordershellrecorderLGPLANYMemory currently used by the queue (in kB)Owen Taylor <otaylor@redhat.com>Feed screen capture data to a pipeline[gnome-shell] shell_recorder_src_memory_used_update_idlehttp://live.gnome.org/GnomeShellshell_recorder_src_add_bufferNaTrayChildGDK_IS_SCREEN (screen)notification_areaicon_window != NoneNA_IS_TRAY_CHILD (child)UTF8_STRING_NET_WM_NAMEna_tray_child_get_wm_classna_tray_child_has_alphana_tray_child_get_titlena_tray_child_neworientationtray_icon_addedtray_icon_removedmessage_sentmessage_cancelledlost_selectionGTK_IS_INVISIBLE (invisible)../src/tray/na-tray-manager.cGDK_IS_WINDOW (window)manager->invisible != NULL_NET_SYSTEM_TRAY_ORIENTATION_NET_SYSTEM_TRAY_COLORSplug_removedNaTrayManagermanager->screen == NULLNA_IS_TRAY_MANAGER (manager)_NET_SYSTEM_TRAY_S%d_NET_SYSTEM_TRAY_VISUALMANAGER_NET_SYSTEM_TRAY_OPCODE_NET_SYSTEM_TRAY_MESSAGE_DATAgtk_widget_get_realized (invisible)na_tray_manager_get_orientationna_tray_manager_set_colorsna_tray_manager_set_orientationna_tray_manager_set_colors_propertyna_tray_manager_set_visual_propertyna_tray_manager_set_orientation_propertyna_tray_manager_manage_screen_x11na_tray_manager_manage_screenna_tray_manager_unmanageGVariant(�

!!!"#%&&''(())*+-.0001125668899::::::;=@@CDFFGHHIIJKMNORUUVVWWXY_```cdddfffffggggjklnpssuwyy{{~��������������N�v(������D��	v��g�-�}?�g�
vx��?k���	v�v���$0+v�L|����X�D��v�����*�܁��v��/���r�/v0/.YIx��.Yv@Y���FA��v��
�"��
vu$���}u$v�$\1O��z�\1vh1��t0}�v0����!T���v��g����gvx�3TS�.��3v�3՛�T"}՛
v��`[����v�z���➀z�v��2�����2�	v@��,��!���,
v�,�3�B6��v���1tdw��1
v�1�<�ܯ?��<v�<>U���VD>UvHUh\"J́h\v�\����}�v�-4ڸ}�-4	v84>a[��G�>avXa���XH(���v���'5s��	v���B	�Fq���B	v�B	u	���}u	v u	μ	[F��μ	v�	��	������	v��	C�	Xᩦ�C�	vP�	J}
��f}J}
v`}
�
O�����
v(�
�
*�pE��
	v�
�F�J���Fv�F�P08���Pv�P��s�ۗ}��	vȯ۲KP�>۲L���*?��v������3}�v�e

D#d�e

vx

#
W�码#
v0#
I<
��V��I<
v`<
�I
�W�.��I
vJ
��
�q�Z���
v��
��
G֐���
	v��
R�9�R
v`�,��	_��,v-83-,��83	vH3r�zm��r�v���딏3��v�v>7��I}v>v�>HDJu�}HDvXDlh�s�
YlhLth|h��S}|hv�h>��h��>�vP���Ե������L����
�7����
v�����>���vލ7�Dލv�+�=�z�+�	v8���������v��T�^��tYT�LX�x�]-��x�v��M�g���DM�vX���;	v e(��/Ձe(vx(%�>��X�%�v8�s�8��́s�v�����f-���v��\ow���v�|;�sp8�|;
v�;:c�^���:c
vHc�P[�D�
v ����\����v��O��i{��Ov�OǍ�],��Ǎv؍G���_�G�
vX���	�v �-��́�-
v�-MG�O��}MGv`GJ5��f}J
vJi�Q6ׁiv0i��7,p���LȾؾ�fN�ؾv���X���v��C�_˾DCvP���f���vȕ��/}�}��
v���q[0ԁ�v�s�/�:�}s�v���!j�ے��!v�!-�5�,�-v-':�/X��':v@:�~a���v �����Xd���v��4���!�}4�	v@���'��;��v���B��N��v�C<
��;�C<vX<pfU�<��pfv�ff{�o��f{vx{J�iI�1}J�v`�1��jXo�1�
v@�׸���׸
v���%ā�v ��I�5���I	v�IvX�e�C�vXv�X�v�4��v
v�v��E�����v���ѻ�(D�v��|��3��
v�=)�n�=)vP)�<������<
v�<z^-�9�z^v�^��;�
����v�i��-�}i�vx���# XՁ��v��A����x�A�vP�ʳr_�
YʳLг�$����v0�����Á���v��>��x�(�>�LH���D԰�Y��L����Kz����v��uB5�uBv�B�F�=i��Fv�F!��uk�!�
v0�ȯ �,[L�ȯ Lԯ � ��k��� 
v� �� ��E�}�� 
v�� � iconGrid.jsN�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Meta, St } = imports.gi;

const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const Main = imports.ui.main;

var ICON_SIZE = 96;
var MIN_ICON_SIZE = 16;

var EXTRA_SPACE_ANIMATION_TIME = 0.25;

var ANIMATION_TIME_IN = 0.350;
var ANIMATION_TIME_OUT = 1/2 * ANIMATION_TIME_IN;
var ANIMATION_MAX_DELAY_FOR_ITEM = 2/3 * ANIMATION_TIME_IN;
var ANIMATION_BASE_DELAY_FOR_ITEM = 1/4 * ANIMATION_MAX_DELAY_FOR_ITEM;
var ANIMATION_MAX_DELAY_OUT_FOR_ITEM = 2/3 * ANIMATION_TIME_OUT;
var ANIMATION_FADE_IN_TIME_FOR_ITEM = 1/4 * ANIMATION_TIME_IN;

var ANIMATION_BOUNCE_ICON_SCALE = 1.1;

var AnimationDirection = {
    IN: 0,
    OUT: 1
};

var APPICON_ANIMATION_OUT_SCALE = 3;
var APPICON_ANIMATION_OUT_TIME = 0.25;

var BaseIcon = GObject.registerClass(
class BaseIcon extends St.Bin {
    _init(label, params) {
        params = Params.parse(params, { createIcon: null,
                                        setSizeManually: false,
                                        showLabel: true });

        let styleClass = 'overview-icon';
        if (params.showLabel)
            styleClass += ' overview-icon-with-label';

        super._init({ style_class: styleClass,
                      x_fill: true,
                      y_fill: true });

        this.actor = this;

        this.connect('destroy', this._onDestroy.bind(this));

        this._box = new St.BoxLayout({ vertical: true });
        this.set_child(this._box);

        this.iconSize = ICON_SIZE;
        this._iconBin = new St.Bin({ x_align: St.Align.MIDDLE,
                                     y_align: St.Align.MIDDLE });

        this._box.add_actor(this._iconBin);

        if (params.showLabel) {
            this.label = new St.Label({ text: label });
            this._box.add_actor(this.label);
        } else {
            this.label = null;
        }

        if (params.createIcon)
            this.createIcon = params.createIcon;
        this._setSizeManually = params.setSizeManually;

        this.icon = null;

        let cache = St.TextureCache.get_default();
        this._iconThemeChangedId = cache.connect('icon-theme-changed', this._onIconThemeChanged.bind(this));
    }

    vfunc_get_preferred_width(forHeight) {
        // Return the actual height to keep the squared aspect
        return this.get_preferred_height(-1);
    }

    // This can be overridden by a subclass, or by the createIcon
    // parameter to _init()
    createIcon(size) {
        throw new Error('no implementation of createIcon in ' + this);
    }

    setIconSize(size) {
        if (!this._setSizeManually)
            throw new Error('setSizeManually has to be set to use setIconsize');

        if (size == this.iconSize)
            return;

        this._createIconTexture(size);
    }

    _createIconTexture(size) {
        if (this.icon)
            this.icon.destroy();
        this.iconSize = size;
        this.icon = this.createIcon(this.iconSize);

        this._iconBin.child = this.icon;
    }

    vfunc_style_changed() {
        super.vfunc_style_changed();
        let node = this.get_theme_node();

        let size;
        if (this._setSizeManually) {
            size = this.iconSize;
        } else {
            let [found, len] = node.lookup_length('icon-size', false);
            size = found ? len : ICON_SIZE;
        }

        if (this.iconSize == size && this._iconBin.child)
            return;

        this._createIconTexture(size);
    }

    _onDestroy() {
        if (this._iconThemeChangedId > 0) {
            let cache = St.TextureCache.get_default();
            cache.disconnect(this._iconThemeChangedId);
            this._iconThemeChangedId = 0;
        }
    }

    _onIconThemeChanged() {
        this._createIconTexture(this.iconSize);
    }

    animateZoomOut() {
        // Animate only the child instead of the entire actor, so the
        // styles like hover and running are not applied while
        // animating.
        zoomOutActor(this.child);
    }
});

function clamp(value, min, max) {
    return Math.max(Math.min(value, max), min);
};

function zoomOutActor(actor) {
    let actorClone = new Clutter.Clone({ source: actor,
                                         reactive: false });
    let [width, height] = actor.get_transformed_size();
    let [x, y] = actor.get_transformed_position();
    actorClone.set_size(width, height);
    actorClone.set_position(x, y);
    actorClone.opacity = 255;
    actorClone.set_pivot_point(0.5, 0.5);

    Main.uiGroup.add_actor(actorClone);

    // Avoid monitor edges to not zoom outside the current monitor
    let monitor = Main.layoutManager.findMonitorForActor(actor);
    let scaledWidth = width * APPICON_ANIMATION_OUT_SCALE;
    let scaledHeight = height * APPICON_ANIMATION_OUT_SCALE;
    let scaledX = x - (scaledWidth - width) / 2;
    let scaledY = y - (scaledHeight - height) / 2;
    let containedX = clamp(scaledX, monitor.x, monitor.x + monitor.width - scaledWidth);
    let containedY = clamp(scaledY, monitor.y, monitor.y + monitor.height - scaledHeight);

    Tweener.addTween(actorClone,
                     { time: APPICON_ANIMATION_OUT_TIME,
                       scale_x: APPICON_ANIMATION_OUT_SCALE,
                       scale_y: APPICON_ANIMATION_OUT_SCALE,
                       translation_x: containedX - scaledX,
                       translation_y: containedY - scaledY,
                       opacity: 0,
                       transition: 'easeOutQuad',
                       onComplete() {
                           actorClone.destroy();
                       }
                    });
}

var IconGrid = GObject.registerClass({
    Signals: {'animation-done': {},
              'child-focused': { param_types: [Clutter.Actor.$gtype]} },
}, class IconGrid extends St.Widget {
    _init(params) {
        super._init({ style_class: 'icon-grid',
                      y_align: Clutter.ActorAlign.START });

        this.actor = this;

        params = Params.parse(params, { rowLimit: null,
                                        columnLimit: null,
                                        minRows: 1,
                                        minColumns: 1,
                                        fillParent: false,
                                        xAlign: St.Align.MIDDLE,
                                        padWithSpacing: false });
        this._rowLimit = params.rowLimit;
        this._colLimit = params.columnLimit;
        this._minRows = params.minRows;
        this._minColumns = params.minColumns;
        this._xAlign = params.xAlign;
        this._fillParent = params.fillParent;
        this._padWithSpacing = params.padWithSpacing;

        this.topPadding = 0;
        this.bottomPadding = 0;
        this.rightPadding = 0;
        this.leftPadding = 0;

        this._updateIconSizesLaterId = 0;

        this._items = [];
        this._clonesAnimating = [];
        // Pulled from CSS, but hardcode some defaults here
        this._spacing = 0;
        this._hItemSize = this._vItemSize = ICON_SIZE;
        this._fixedHItemSize = this._fixedVItemSize = undefined;
        this.connect('style-changed', this._onStyleChanged.bind(this));

        // Cancel animations when hiding the overview, to avoid icons
        // swarming into the void ...
        this.connect('notify::mapped', () => {
            if (!this.mapped)
                this._cancelAnimation();
        });

        this.connect('actor-added', this._childAdded.bind(this));
        this.connect('actor-removed', this._childRemoved.bind(this));
        this.connect('destroy', this._onDestroy.bind(this));
    }

    _onDestroy() {
        if (this._updateIconSizesLaterId) {
            Meta.later_remove (this._updateIconSizesLaterId);
            this._updateIconSizesLaterId = 0;
        }
    }

    _keyFocusIn(actor) {
        this.emit('child-focused', actor);
    }

    _childAdded(grid, child) {
        child._iconGridKeyFocusInId = child.connect('key-focus-in', this._keyFocusIn.bind(this));
    }

    _childRemoved(grid, child) {
        child.disconnect(child._iconGridKeyFocusInId);
    }

    vfunc_get_preferred_width(forHeight) {
        if (this._fillParent)
            // Ignore all size requests of children and request a size of 0;
            // later we'll allocate as many children as fit the parent
            return [0, 0];

        let nChildren = this.get_n_children();
        let nColumns = this._colLimit ? Math.min(this._colLimit,
                                                 nChildren)
                                      : nChildren;
        let totalSpacing = Math.max(0, nColumns - 1) * this._getSpacing();
        // Kind of a lie, but not really an issue right now.  If
        // we wanted to support some sort of hidden/overflow that would
        // need higher level design
        let minSize = this._getHItemSize() + this.leftPadding + this.rightPadding;
        let natSize = nColumns * this._getHItemSize() + totalSpacing + this.leftPadding + this.rightPadding;

        return this.get_theme_node().adjust_preferred_width(minSize, natSize);
    }

    _getVisibleChildren() {
        return this.get_children().filter(actor => actor.visible);
    }

    vfunc_get_preferred_height(forWidth) {
        if (this._fillParent)
            // Ignore all size requests of children and request a size of 0;
            // later we'll allocate as many children as fit the parent
            return [0, 0];

        let themeNode = this.get_theme_node();
        let children = this._getVisibleChildren();
        let nColumns;

        forWidth = themeNode.adjust_for_width(forWidth);

        if (forWidth < 0)
            nColumns = children.length;
        else
            [nColumns, ] = this._computeLayout(forWidth);

        let nRows;
        if (nColumns > 0)
            nRows = Math.ceil(children.length / nColumns);
        else
            nRows = 0;
        if (this._rowLimit)
            nRows = Math.min(nRows, this._rowLimit);
        let totalSpacing = Math.max(0, nRows - 1) * this._getSpacing();
        let height = nRows * this._getVItemSize() + totalSpacing + this.topPadding + this.bottomPadding;

        return themeNode.adjust_preferred_height(height, height);
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let themeNode = this.get_theme_node();
        box = themeNode.get_content_box(box);

        if (this._fillParent) {
            // Reset the passed in box to fill the parent
            let parentBox = this.get_parent().allocation;
            let gridBox = themeNode.get_content_box(parentBox);
            box = themeNode.get_content_box(gridBox);
        }

        let children = this._getVisibleChildren();
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;
        let spacing = this._getSpacing();
        let [nColumns, usedWidth] = this._computeLayout(availWidth);

        let leftEmptySpace;
        switch(this._xAlign) {
            case St.Align.START:
                leftEmptySpace = 0;
                break;
            case St.Align.MIDDLE:
                leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
                break;
            case St.Align.END:
                leftEmptySpace = availWidth - usedWidth;
        }

        let x = box.x1 + leftEmptySpace + this.leftPadding;
        let y = box.y1 + this.topPadding;
        let columnIndex = 0;
        let rowIndex = 0;
        for (let i = 0; i < children.length; i++) {
            let childBox = this._calculateChildBox(children[i], x, y, box);

            if (this._rowLimit && rowIndex >= this._rowLimit ||
                this._fillParent && childBox.y2 > availHeight - this.bottomPadding) {
                children[i].hide();
            } else {
                children[i].allocate(childBox, flags);
                children[i].show();
            }

            columnIndex++;
            if (columnIndex == nColumns) {
                columnIndex = 0;
                rowIndex++;
            }

            if (columnIndex == 0) {
                y += this._getVItemSize() + spacing;
                x = box.x1 + leftEmptySpace + this.leftPadding;
            } else {
                x += this._getHItemSize() + spacing;
            }
        }
    }

    vfunc_get_paint_volume(paintVolume) {
        // Setting the paint volume does not make sense when we don't have
        // any allocation
        if (!this.has_allocation())
            return false;

        let themeNode = this.get_theme_node();
        let allocationBox = this.get_allocation_box();
        let paintBox = themeNode.get_paint_box(allocationBox);

        let origin = new Clutter.Vertex();
        origin.x = paintBox.x1 - allocationBox.x1;
        origin.y = paintBox.y1 - allocationBox.y1;
        origin.z = 0.0;

        paintVolume.set_origin(origin);
        paintVolume.set_width(paintBox.x2 - paintBox.x1);
        paintVolume.set_height(paintBox.y2 - paintBox.y1);

        if (this.get_clip_to_allocation())
            return true;

        for (let child = this.get_first_child();
             child != null;
             child = child.get_next_sibling()) {

            if (!child.visible)
                continue;

            let childVolume = child.get_transformed_paint_volume(this);
            if (!childVolume)
                return false

            paintVolume.union(childVolume);
        }

        return true;
    }

    /**
     * Intended to be override by subclasses if they need a different
     * set of items to be animated.
     */
    _getChildrenToAnimate() {
        return this._getVisibleChildren();
    }

    _cancelAnimation() {
        this._clonesAnimating.forEach(clone => { clone.destroy(); });
        this._clonesAnimating = [];
    }

    _animationDone() {
        this._clonesAnimating.forEach(clone => {
            clone.source.reactive = true;
            clone.source.opacity = 255;
            clone.destroy();
        });
        this._clonesAnimating = [];
        this.emit('animation-done');
    }

    animatePulse(animationDirection) {
        if (animationDirection != AnimationDirection.IN)
            throw new Error("Pulse animation only implements 'in' animation direction");

        this._cancelAnimation();

        let actors = this._getChildrenToAnimate();
        if (actors.length == 0) {
            this._animationDone();
            return;
        }

        // For few items the animation can be slow, so use a smaller
        // delay when there are less than 4 items
        // (ANIMATION_BASE_DELAY_FOR_ITEM = 1/4 *
        // ANIMATION_MAX_DELAY_FOR_ITEM)
        let maxDelay = Math.min(ANIMATION_BASE_DELAY_FOR_ITEM * actors.length,
                                ANIMATION_MAX_DELAY_FOR_ITEM);

        for (let index = 0; index < actors.length; index++) {
            let actor = actors[index];
            actor.set_scale(0, 0);
            actor.set_pivot_point(0.5, 0.5);

            let delay = index / actors.length * maxDelay;
            let bounceUpTime = ANIMATION_TIME_IN / 4;
            let isLastItem = index == actors.length - 1;
            Tweener.addTween(actor,
                            { time: bounceUpTime,
                              transition: 'easeInOutQuad',
                              delay: delay,
                              scale_x: ANIMATION_BOUNCE_ICON_SCALE,
                              scale_y: ANIMATION_BOUNCE_ICON_SCALE,
                              onComplete: () => {
                                  Tweener.addTween(actor,
                                                   { time: ANIMATION_TIME_IN - bounceUpTime,
                                                     transition: 'easeInOutQuad',
                                                     scale_x: 1,
                                                     scale_y: 1,
                                                     onComplete: () => {
                                                        if (isLastItem)
                                                            this._animationDone();
                                                    }
                                                   });
                              }
                            });
        }
    }

    animateSpring(animationDirection, sourceActor) {
        this._cancelAnimation();

        let actors = this._getChildrenToAnimate();
        if (actors.length == 0) {
            this._animationDone();
            return;
        }

        let [sourceX, sourceY] = sourceActor.get_transformed_position();
        let [sourceWidth, sourceHeight] = sourceActor.get_size();
        // Get the center
        let [sourceCenterX, sourceCenterY] = [sourceX + sourceWidth / 2, sourceY + sourceHeight / 2];
        // Design decision, 1/2 of the source actor size.
        let [sourceScaledWidth, sourceScaledHeight] = [sourceWidth / 2, sourceHeight / 2];

        actors.forEach(actor => {
            let [actorX, actorY] = actor._transformedPosition = actor.get_transformed_position();
            let [x, y] = [actorX - sourceX, actorY - sourceY];
            actor._distance = Math.sqrt(x * x + y * y);
        });
        let maxDist = actors.reduce((prev, cur) => {
            return Math.max(prev, cur._distance);
        }, 0);
        let minDist = actors.reduce((prev, cur) => {
            return Math.min(prev, cur._distance);
        }, Infinity);
        let normalization = maxDist - minDist;

        for (let index = 0; index < actors.length; index++) {
            let actor = actors[index];
            actor.opacity = 0;
            actor.reactive = false;

            let actorClone = new Clutter.Clone({ source: actor });
            this._clonesAnimating.push(actorClone);
            Main.uiGroup.add_actor(actorClone);

            let [width, height,,] = this._getAllocatedChildSizeAndSpacing(actor);
            actorClone.set_size(width, height);
            let scaleX = sourceScaledWidth / width;
            let scaleY = sourceScaledHeight / height;
            let [adjustedSourcePositionX, adjustedSourcePositionY] = [sourceCenterX - sourceScaledWidth / 2, sourceCenterY - sourceScaledHeight / 2];

            let movementParams, fadeParams;
            if (animationDirection == AnimationDirection.IN) {
                let isLastItem = actor._distance == minDist;

                actorClone.opacity = 0;
                actorClone.set_scale(scaleX, scaleY);

                actorClone.set_position(adjustedSourcePositionX, adjustedSourcePositionY);

                let delay = (1 - (actor._distance - minDist) / normalization) * ANIMATION_MAX_DELAY_FOR_ITEM;
                let [finalX, finalY]  = actor._transformedPosition;
                movementParams = { time: ANIMATION_TIME_IN,
                                   transition: 'easeInOutQuad',
                                   delay: delay,
                                   x: finalX,
                                   y: finalY,
                                   scale_x: 1,
                                   scale_y: 1,
                                   onComplete: () => {
                                       if (isLastItem)
                                           this._animationDone();
                                   }};
                fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
                               transition: 'easeInOutQuad',
                               delay: delay,
                               opacity: 255 };
            } else {
                let isLastItem = actor._distance == maxDist;

                let [startX, startY]  = actor._transformedPosition;
                actorClone.set_position(startX, startY);

                let delay = (actor._distance - minDist) / normalization * ANIMATION_MAX_DELAY_OUT_FOR_ITEM;
                movementParams = { time: ANIMATION_TIME_OUT,
                                   transition: 'easeInOutQuad',
                                   delay: delay,
                                   x: adjustedSourcePositionX,
                                   y: adjustedSourcePositionY,
                                   scale_x: scaleX,
                                   scale_y: scaleY,
                                   onComplete: () => {
                                       if (isLastItem)
                                           this._animationDone();
                                   }};
                fadeParams = { time: ANIMATION_FADE_IN_TIME_FOR_ITEM,
                               transition: 'easeInOutQuad',
                               delay: ANIMATION_TIME_OUT + delay - ANIMATION_FADE_IN_TIME_FOR_ITEM,
                               opacity: 0 };
            }


            Tweener.addTween(actorClone, movementParams);
            Tweener.addTween(actorClone, fadeParams);
        }
    }

    _getAllocatedChildSizeAndSpacing(child) {
        let [,, natWidth, natHeight] = child.get_preferred_size();
        let width = Math.min(this._getHItemSize(), natWidth);
        let xSpacing = Math.max(0, width - natWidth) / 2;
        let height = Math.min(this._getVItemSize(), natHeight);
        let ySpacing = Math.max(0, height - natHeight) / 2;
        return [width, height, xSpacing, ySpacing];
    }

    _calculateChildBox(child, x, y, box) {
        /* Center the item in its allocation horizontally */
        let [width, height, childXSpacing, childYSpacing] =
            this._getAllocatedChildSizeAndSpacing(child);

        let childBox = new Clutter.ActorBox();
        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
            let _x = box.x2 - (x + width);
            childBox.x1 = Math.floor(_x - childXSpacing);
        } else {
            childBox.x1 = Math.floor(x + childXSpacing);
        }
        childBox.y1 = Math.floor(y + childYSpacing);
        childBox.x2 = childBox.x1 + width;
        childBox.y2 = childBox.y1 + height;
        return childBox;
    }

    columnsForWidth(rowWidth) {
        return this._computeLayout(rowWidth)[0];
    }

    getRowLimit() {
        return this._rowLimit;
    }

    _computeLayout(forWidth) {
        let nColumns = 0;
        let usedWidth = this.leftPadding + this.rightPadding;
        let spacing = this._getSpacing();

        while ((this._colLimit == null || nColumns < this._colLimit) &&
               (usedWidth + this._getHItemSize() <= forWidth)) {
            usedWidth += this._getHItemSize() + spacing;
            nColumns += 1;
        }

        if (nColumns > 0)
            usedWidth -= spacing;

        return [nColumns, usedWidth];
    }

    _onStyleChanged() {
        let themeNode = this.get_theme_node();
        this._spacing = themeNode.get_length('spacing');
        this._hItemSize = themeNode.get_length('-shell-grid-horizontal-item-size') || ICON_SIZE;
        this._vItemSize = themeNode.get_length('-shell-grid-vertical-item-size') || ICON_SIZE;
        this.queue_relayout();
    }

    nRows(forWidth) {
        let children = this._getVisibleChildren();
        let nColumns = (forWidth < 0) ? children.length : this._computeLayout(forWidth)[0];
        let nRows = (nColumns > 0) ? Math.ceil(children.length / nColumns) : 0;
        if (this._rowLimit)
            nRows = Math.min(nRows, this._rowLimit);
        return nRows;
    }

    rowsForHeight(forHeight) {
        return Math.floor((forHeight - (this.topPadding + this.bottomPadding) + this._getSpacing()) / (this._getVItemSize() + this._getSpacing()));
    }

    usedHeightForNRows(nRows) {
        return (this._getVItemSize() + this._getSpacing()) * nRows - this._getSpacing() + this.topPadding + this.bottomPadding;
    }

    usedWidth(forWidth) {
        return this.usedWidthForNColumns(this.columnsForWidth(forWidth));
    }

    usedWidthForNColumns(columns) {
        let usedWidth = columns  * (this._getHItemSize() + this._getSpacing());
        usedWidth -= this._getSpacing();
        return usedWidth + this.leftPadding + this.rightPadding;
    }

    removeAll() {
        this._items = [];
        this.remove_all_children();
    }

    destroyAll() {
        this._items = [];
        this.destroy_all_children();
    }

    addItem(item, index) {
        if (!(item.icon instanceof BaseIcon))
            throw new Error('Only items with a BaseIcon icon property can be added to IconGrid');

        this._items.push(item);
        if (index !== undefined)
            this.insert_child_at_index(item.actor, index);
        else
            this.add_actor(item.actor);
    }

    removeItem(item) {
        this.remove_child(item.actor);
    }

    getItemAtIndex(index) {
        return this.get_child_at_index(index);
    }

    visibleItemsCount() {
        return this.get_children().filter(c => c.is_visible()).length;
    }

    setSpacing(spacing) {
        this._fixedSpacing = spacing;
    }

    _getSpacing() {
        return this._fixedSpacing ? this._fixedSpacing : this._spacing;
    }

    _getHItemSize() {
        return this._fixedHItemSize ? this._fixedHItemSize : this._hItemSize;
    }

    _getVItemSize() {
        return this._fixedVItemSize ? this._fixedVItemSize : this._vItemSize;
    }

    _updateSpacingForSize(availWidth, availHeight) {
        let maxEmptyVArea = availHeight - this._minRows * this._getVItemSize();
        let maxEmptyHArea = availWidth - this._minColumns * this._getHItemSize();
        let maxHSpacing, maxVSpacing;

        if (this._padWithSpacing) {
            // minRows + 1 because we want to put spacing before the first row, so it is like we have one more row
            // to divide the empty space
            maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows +1));
            maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns +1));
        } else {
            if (this._minRows <=  1)
                maxVSpacing = maxEmptyVArea;
            else
                maxVSpacing = Math.floor(maxEmptyVArea / (this._minRows - 1));

            if (this._minColumns <=  1)
                maxHSpacing = maxEmptyHArea;
            else
                maxHSpacing = Math.floor(maxEmptyHArea / (this._minColumns - 1));
        }

        let maxSpacing = Math.min(maxHSpacing, maxVSpacing);
        // Limit spacing to the item size
        maxSpacing = Math.min(maxSpacing, Math.min(this._getVItemSize(), this._getHItemSize()));
        // The minimum spacing, regardless of whether it satisfies the row/columng minima,
        // is the spacing we get from CSS.
        let spacing = Math.max(this._spacing, maxSpacing);
        this.setSpacing(spacing);
        if (this._padWithSpacing)
            this.topPadding = this.rightPadding = this.bottomPadding = this.leftPadding = spacing;
    }

    /**
     * This function must to be called before iconGrid allocation,
     * to know how much spacing can the grid has
     */
    adaptToSize(availWidth, availHeight) {
        this._fixedHItemSize = this._hItemSize;
        this._fixedVItemSize = this._vItemSize;
        this._updateSpacingForSize(availWidth, availHeight);
        let spacing = this._getSpacing();

        if (this.columnsForWidth(availWidth) < this._minColumns || this.rowsForHeight(availHeight) < this._minRows) {
            let neededWidth = this.usedWidthForNColumns(this._minColumns) - availWidth ;
            let neededHeight = this.usedHeightForNRows(this._minRows) - availHeight ;

            let neededSpacePerItem = (neededWidth > neededHeight) ? Math.ceil(neededWidth / this._minColumns)
                                                                  : Math.ceil(neededHeight / this._minRows);
            this._fixedHItemSize = Math.max(this._hItemSize - neededSpacePerItem, MIN_ICON_SIZE);
            this._fixedVItemSize = Math.max(this._vItemSize - neededSpacePerItem, MIN_ICON_SIZE);

            this._updateSpacingForSize(availWidth, availHeight);
        }
        if (!this._updateIconSizesLaterId)
            this._updateIconSizesLaterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
                                                          this._updateIconSizes.bind(this));
    }

    // Note that this is ICON_SIZE as used by BaseIcon, not elsewhere in IconGrid; it's a bit messed up
    _updateIconSizes() {
        this._updateIconSizesLaterId = 0;
        let scale = Math.min(this._fixedHItemSize, this._fixedVItemSize) / Math.max(this._hItemSize, this._vItemSize);
        let newIconSize = Math.floor(ICON_SIZE * scale);
        for (let i in this._items) {
            this._items[i].icon.setIconSize(newIconSize);
        }
    }
});

var PaginatedIconGrid = GObject.registerClass({
    Signals: {'space-opened': {},
              'space-closed': {} },
}, class PaginatedIconGrid extends IconGrid {
    _init(params) {
        super._init(params);
        this._nPages = 0;
        this.currentPage = 0;
        this._rowsPerPage = 0;
        this._spaceBetweenPages = 0;
        this._childrenPerPage = 0;
    }

    vfunc_get_preferred_height(forWidth) {
        let height = (this._availableHeightPerPageForItems() + this.bottomPadding + this.topPadding) * this._nPages + this._spaceBetweenPages * this._nPages;
        return [height, height];
    }

    vfunc_allocate(box, flags) {
         if (this._childrenPerPage == 0)
            log('computePages() must be called before allocate(); pagination will not work.');

        this.set_allocation(box, flags);

        if (this._fillParent) {
            // Reset the passed in box to fill the parent
            let parentBox = this.get_parent().allocation;
            let gridBox = this.get_theme_node().get_content_box(parentBox);
            box = this.get_theme_node().get_content_box(gridBox);
        }
        let children = this._getVisibleChildren();
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;
        let spacing = this._getSpacing();
        let [nColumns, usedWidth] = this._computeLayout(availWidth);

        let leftEmptySpace;
        switch(this._xAlign) {
            case St.Align.START:
                leftEmptySpace = 0;
                break;
            case St.Align.MIDDLE:
                leftEmptySpace = Math.floor((availWidth - usedWidth) / 2);
                break;
            case St.Align.END:
                leftEmptySpace = availWidth - usedWidth;
        }

        let x = box.x1 + leftEmptySpace + this.leftPadding;
        let y = box.y1 + this.topPadding;
        let columnIndex = 0;
        let rowIndex = 0;

        for (let i = 0; i < children.length; i++) {
            let childBox = this._calculateChildBox(children[i], x, y, box);
            children[i].allocate(childBox, flags);
            children[i].show();

            columnIndex++;
            if (columnIndex == nColumns) {
                columnIndex = 0;
                rowIndex++;
            }
            if (columnIndex == 0) {
                y += this._getVItemSize() + spacing;
                if ((i + 1) % this._childrenPerPage == 0)
                    y +=  this._spaceBetweenPages - spacing + this.bottomPadding + this.topPadding;
                x = box.x1 + leftEmptySpace + this.leftPadding;
            } else
                x += this._getHItemSize() + spacing;
        }
    }

    // Overriden from IconGrid
    _getChildrenToAnimate() {
        let children = this._getVisibleChildren();
        let firstIndex = this._childrenPerPage * this.currentPage;
        let lastIndex = firstIndex + this._childrenPerPage;

        return children.slice(firstIndex, lastIndex);
    }

    _computePages(availWidthPerPage, availHeightPerPage) {
        let [nColumns, usedWidth] = this._computeLayout(availWidthPerPage);
        let nRows;
        let children = this._getVisibleChildren();
        if (nColumns > 0)
            nRows = Math.ceil(children.length / nColumns);
        else
            nRows = 0;
        if (this._rowLimit)
            nRows = Math.min(nRows, this._rowLimit);

        let spacing = this._getSpacing();
        // We want to contain the grid inside the parent box with padding
        this._rowsPerPage = this.rowsForHeight(availHeightPerPage);
        this._nPages = Math.ceil(nRows / this._rowsPerPage);
        this._spaceBetweenPages = availHeightPerPage - (this.topPadding + this.bottomPadding) - this._availableHeightPerPageForItems();
        this._childrenPerPage = nColumns * this._rowsPerPage;
    }

    adaptToSize(availWidth, availHeight) {
        super.adaptToSize(availWidth, availHeight);
        this._computePages(availWidth, availHeight);
    }

    _availableHeightPerPageForItems() {
        return this.usedHeightForNRows(this._rowsPerPage) - (this.topPadding + this.bottomPadding);
    }

    nPages() {
        return this._nPages;
    }

    getPageHeight() {
        return this._availableHeightPerPageForItems();
    }

    getPageY(pageNumber) {
        if (!this._nPages)
            return 0;

        let firstPageItem = pageNumber * this._childrenPerPage
        let childBox = this._getVisibleChildren()[firstPageItem].get_allocation_box();
        return childBox.y1 - this.topPadding;
    }

    getItemPage(item) {
        let children = this._getVisibleChildren();
        let index = children.indexOf(item);
        if (index == -1)
            throw new Error('Item not found.');
        return Math.floor(index / this._childrenPerPage);
    }

    /**
    * openExtraSpace:
    * @sourceItem: the item for which to create extra space
    * @side: where @sourceItem should be located relative to the created space
    * @nRows: the amount of space to create
    *
    * Pan view to create extra space for @nRows above or below @sourceItem.
    */
    openExtraSpace(sourceItem, side, nRows) {
        let children = this._getVisibleChildren();
        let index = children.indexOf(sourceItem.actor);
        if (index == -1)
            throw new Error('Item not found.');

        let pageIndex = Math.floor(index / this._childrenPerPage);
        let pageOffset = pageIndex * this._childrenPerPage;

        let childrenPerRow = this._childrenPerPage / this._rowsPerPage;
        let sourceRow = Math.floor((index - pageOffset) / childrenPerRow);

        let nRowsAbove = (side == St.Side.TOP) ? sourceRow + 1
                                               : sourceRow;
        let nRowsBelow = this._rowsPerPage - nRowsAbove;

        let nRowsUp, nRowsDown;
        if (side == St.Side.TOP) {
            nRowsDown = Math.min(nRowsBelow, nRows);
            nRowsUp = nRows - nRowsDown;
        } else {
            nRowsUp = Math.min(nRowsAbove, nRows);
            nRowsDown = nRows - nRowsUp;
        }

        let childrenDown = children.splice(pageOffset +
                                           nRowsAbove * childrenPerRow,
                                           nRowsBelow * childrenPerRow);
        let childrenUp = children.splice(pageOffset,
                                         nRowsAbove * childrenPerRow);

        // Special case: On the last row with no rows below the icon,
        // there's no need to move any rows either up or down
        if (childrenDown.length == 0 && nRowsUp == 0) {
            this._translatedChildren = [];
            this.emit('space-opened');
        } else {
            this._translateChildren(childrenUp, St.DirectionType.UP, nRowsUp);
            this._translateChildren(childrenDown, St.DirectionType.DOWN, nRowsDown);
            this._translatedChildren = childrenUp.concat(childrenDown);
        }
    }

    _translateChildren(children, direction, nRows) {
        let translationY = nRows * (this._getVItemSize() + this._getSpacing());
        if (translationY == 0)
            return;

        if (direction == St.DirectionType.UP)
            translationY *= -1;

        for (let i = 0; i < children.length; i++) {
            children[i].translation_y = 0;
            let params = { translation_y: translationY,
                           time: EXTRA_SPACE_ANIMATION_TIME,
                           transition: 'easeInOutQuad'
                         };
            if (i == (children.length - 1))
                params.onComplete = () => { this.emit('space-opened'); };
            Tweener.addTween(children[i], params);
        }
    }

    closeExtraSpace() {
        if (!this._translatedChildren || !this._translatedChildren.length) {
            this.emit('space-closed');
            return;
        }

        for (let i = 0; i < this._translatedChildren.length; i++) {
            if (!this._translatedChildren[i].translation_y)
                continue;
            Tweener.addTween(this._translatedChildren[i],
                             { translation_y: 0,
                               time: EXTRA_SPACE_ANIMATION_TIME,
                               transition: 'easeInOutQuad',
                               onComplete: () => { this.emit('space-closed'); }
                             });
        }
    }
});
(uuay)realmd.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;
const Signals = imports.signals;

const { loadInterfaceXML } = imports.misc.fileUtils;

const ProviderIface = loadInterfaceXML("org.freedesktop.realmd.Provider");
const Provider = Gio.DBusProxy.makeProxyWrapper(ProviderIface);

const ServiceIface = loadInterfaceXML("org.freedesktop.realmd.Service");
const Service = Gio.DBusProxy.makeProxyWrapper(ServiceIface);

const RealmIface = loadInterfaceXML("org.freedesktop.realmd.Realm");
const Realm = Gio.DBusProxy.makeProxyWrapper(RealmIface);

var Manager = class {
    constructor(parentActor) {
        this._aggregateProvider = Provider(Gio.DBus.system,
                                           'org.freedesktop.realmd',
                                           '/org/freedesktop/realmd',
                                           this._reloadRealms.bind(this))
        this._realms = {};
        this._loginFormat = null;

        this._signalId = this._aggregateProvider.connect('g-properties-changed',
            (proxy, properties) => {
                if ('Realms' in properties.deep_unpack())
                    this._reloadRealms();
            });
    }

    _reloadRealms() {
        let realmPaths = this._aggregateProvider.Realms;

        if (!realmPaths)
            return;

        for (let i = 0; i < realmPaths.length; i++) {
            let realm = Realm(Gio.DBus.system,
                              'org.freedesktop.realmd',
                              realmPaths[i],
                              this._onRealmLoaded.bind(this));
        }
    }

    _reloadRealm(realm) {
        if (!realm.Configured) {
            if (this._realms[realm.get_object_path()])
                delete this._realms[realm.get_object_path()];

            return;
        }

        this._realms[realm.get_object_path()] = realm;

        this._updateLoginFormat();
    }

    _onRealmLoaded(realm, error) {
        if (error)
            return;

        this._reloadRealm(realm);

        realm.connect('g-properties-changed', (proxy, properties) => {
            if ('Configured' in properties.deep_unpack())
                this._reloadRealm(realm);
        });
    }

    _updateLoginFormat() {
        let newLoginFormat;

        for (let realmPath in this._realms) {
            let realm = this._realms[realmPath];
            if (realm.LoginFormats && realm.LoginFormats.length > 0) {
                newLoginFormat = realm.LoginFormats[0];
                break;
            }
        }

        if (this._loginFormat != newLoginFormat) {
            this._loginFormat = newLoginFormat;
            this.emit('login-format-changed', newLoginFormat);
        }
    }

    get loginFormat() {
        if (this._loginFormat)
            return this._loginFormat;

        this._updateLoginFormat();

        return this._loginFormat;
    }

    release() {
        Service(Gio.DBus.system,
                'org.freedesktop.realmd',
                '/org/freedesktop/realmd',
                service => { service.ReleaseRemote(); });
        this._aggregateProvider.disconnect(this._signalId);
        this._realms = { };
        this._updateLoginFormat();
    }
};
Signals.addSignalMethods(Manager.prototype)
(uuay)screencast.js_// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'media-record-symbolic';
        this._indicator.add_style_class_name('screencast-indicator');
        this._sync();

        Main.screencastService.connect('updated', this._sync.bind(this));
    }

    _sync() {
        this._indicator.visible = Main.screencastService.isRecording;
    }
};
(uuay)dialog.jsv // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GObject, Pango, St } = imports.gi;

var Dialog = GObject.registerClass(
class Dialog extends St.Widget {
    _init(parentActor, styleClass) {
        super._init({ layout_manager: new Clutter.BinLayout() });
        this.connect('destroy', this._onDestroy.bind(this));

        this._initialKeyFocus = null;
        this._initialKeyFocusDestroyId = 0;
        this._pressedKey = null;
        this._buttonKeys = {};
        this._createDialog();
        this.add_child(this._dialog);

        if (styleClass != null)
            this._dialog.add_style_class_name(styleClass);

        this._parentActor = parentActor;
        this._eventId = this._parentActor.connect('event', this._modalEventHandler.bind(this));
        this._parentActor.add_child(this);
    }

    _createDialog() {
        this._dialog = new St.BoxLayout({ style_class: 'modal-dialog',
                                          x_align:     Clutter.ActorAlign.CENTER,
                                          y_align:     Clutter.ActorAlign.CENTER,
                                          vertical:    true });

        // modal dialogs are fixed width and grow vertically; set the request
        // mode accordingly so wrapped labels are handled correctly during
        // size requests.
        this._dialog.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
        this._dialog.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);

        this.contentLayout = new St.BoxLayout({ vertical: true,
                                                style_class: "modal-dialog-content-box" });
        this._dialog.add(this.contentLayout,
                         { expand:  true,
                           x_fill:  true,
                           y_fill:  true,
                           x_align: St.Align.MIDDLE,
                           y_align: St.Align.START });

        this.buttonLayout = new St.Widget ({ layout_manager: new Clutter.BoxLayout({ homogeneous:true }) });
        this._dialog.add(this.buttonLayout,
                         { x_align: St.Align.MIDDLE,
                           y_align: St.Align.START });
    }

    _onDestroy() {
        if (this._eventId != 0)
            this._parentActor.disconnect(this._eventId);
        this._eventId = 0;
    }

    _modalEventHandler(actor, event) {
        if (event.type() == Clutter.EventType.KEY_PRESS) {
            this._pressedKey = event.get_key_symbol();
        } else if (event.type() == Clutter.EventType.KEY_RELEASE) {
            let pressedKey = this._pressedKey;
            this._pressedKey = null;

            let symbol = event.get_key_symbol();
            if (symbol != pressedKey)
                return Clutter.EVENT_PROPAGATE;

            let buttonInfo = this._buttonKeys[symbol];
            if (!buttonInfo)
                return Clutter.EVENT_PROPAGATE;

            let { button, action } = buttonInfo;

            if (action && button.reactive) {
                action();
                return Clutter.EVENT_STOP;
            }
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _setInitialKeyFocus(actor) {
        if (this._initialKeyFocus)
            this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);

        this._initialKeyFocus = actor;

        this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
            this._initialKeyFocus = null;
            this._initialKeyFocusDestroyId = 0;
        });
    }

    get initialKeyFocus() {
        return this._initialKeyFocus || this;
    }

    addContent(actor) {
        this.contentLayout.add (actor, { expand: true });
    }

    addButton(buttonInfo) {
        let { label, action, key } = buttonInfo;
        let isDefault = buttonInfo['default'];
        let keys;

        if (key)
            keys = [key];
        else if (isDefault)
            keys = [Clutter.KEY_Return, Clutter.KEY_KP_Enter, Clutter.KEY_ISO_Enter];
        else
            keys = [];

        let button = new St.Button({ style_class: 'modal-dialog-linked-button',
                                     button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                     reactive:    true,
                                     can_focus:   true,
                                     x_expand:    true,
                                     y_expand:    true,
                                     label:       label });
        button.connect('clicked', action);

        buttonInfo['button'] = button;

        if (isDefault)
            button.add_style_pseudo_class('default');

        if (this._initialKeyFocus == null || isDefault)
            this._setInitialKeyFocus(button);

        for (let i in keys)
            this._buttonKeys[keys[i]] = buttonInfo;

        this.buttonLayout.add_actor(button);

        return button;
    }

    clearButtons() {
        this.buttonLayout.destroy_all_children();
        this._buttonKeys = {};
    }
});

var MessageDialogContent = GObject.registerClass({
    Properties: {
        'icon': GObject.ParamSpec.object('icon', 'icon', 'icon',
                                         GObject.ParamFlags.READWRITE |
                                         GObject.ParamFlags.CONSTRUCT,
                                         Gio.Icon.$gtype),
        'title': GObject.ParamSpec.string('title', 'title', 'title',
                                          GObject.ParamFlags.READWRITE |
                                          GObject.ParamFlags.CONSTRUCT,
                                          null),
        'subtitle': GObject.ParamSpec.string('subtitle', 'subtitle', 'subtitle',
                                             GObject.ParamFlags.READWRITE |
                                             GObject.ParamFlags.CONSTRUCT,
                                             null),
        'body': GObject.ParamSpec.string('body', 'body', 'body',
                                         GObject.ParamFlags.READWRITE |
                                         GObject.ParamFlags.CONSTRUCT,
                                         null)
    }
}, class MessageDialogContent extends St.BoxLayout {
    _init(params) {
        this._icon = new St.Icon({ y_align: Clutter.ActorAlign.START });
        this._title = new St.Label({ style_class: 'headline' });
        this._subtitle = new St.Label();
        this._body = new St.Label();

        ['icon', 'title', 'subtitle', 'body'].forEach(prop => {
            this[`_${prop}`].add_style_class_name(`message-dialog-${prop}`);
        });

        let textProps = { ellipsize: Pango.EllipsizeMode.NONE,
                          line_wrap: true };
        Object.assign(this._subtitle.clutter_text, textProps);
        Object.assign(this._body.clutter_text, textProps);

        if (!params.hasOwnProperty('style_class'))
            params.style_class = 'message-dialog-main-layout';

        super._init(params);

        this.messageBox = new St.BoxLayout({ style_class: 'message-dialog-content',
                                             x_expand: true,
                                             vertical: true });

        this.messageBox.add_actor(this._title);
        this.messageBox.add_actor(this._subtitle);
        this.messageBox.add_actor(this._body);

        this.add_actor(this._icon);
        this.add_actor(this.messageBox);
    }

    get icon() {
        return this._icon.gicon;
    }

    get title() {
        return this._title.text;
    }

    get subtitle() {
        return this._subtitle.text;
    }

    get body() {
        return this._body.text;
    }

    set icon(icon) {
        Object.assign(this._icon, { gicon: icon, visible: icon != null });
        this.notify('icon');
    }

    set title(title) {
        this._setLabel(this._title, 'title', title);
    }

    set subtitle(subtitle) {
        this._setLabel(this._subtitle, 'subtitle', subtitle);
    }

    set body(body) {
        this._setLabel(this._body, 'body', body);
    }

    _setLabel(label, prop, value) {
        Object.assign(label, { text: value || '', visible: value != null });
        this.notify(prop);
    }

    insertBeforeBody(actor) {
        this.messageBox.insert_child_below(actor, this._body);
    }
});
(uuay)gnome/YloginDialog.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
 * Copyright 2011 Red Hat, Inc
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

const { AccountsService, Atk, Clutter, Gdm, Gio,
        GLib, GObject, Meta, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;

const AuthPrompt = imports.gdm.authPrompt;
const Batch = imports.gdm.batch;
const BoxPointer = imports.ui.boxpointer;
const CtrlAltTab = imports.ui.ctrlAltTab;
const GdmUtil = imports.gdm.util;
const Layout = imports.ui.layout;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Realmd = imports.gdm.realmd;
const Tweener = imports.ui.tweener;
const UserWidget = imports.ui.userWidget;

const _FADE_ANIMATION_TIME = 0.25;
const _SCROLL_ANIMATION_TIME = 0.5;
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;
const _LOGO_ICON_HEIGHT = 48;
const _MAX_BOTTOM_MENU_ITEMS = 5;

var UserListItem = class {
    constructor(user) {
        this.user = user;
        this._userChangedId = this.user.connect('changed',
                                                 this._onUserChanged.bind(this));

        let layout = new St.BoxLayout({ vertical: true });
        this.actor = new St.Button({ style_class: 'login-dialog-user-list-item',
                                     button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                     can_focus: true,
                                     child: layout,
                                     reactive: true,
                                     x_align: St.Align.START,
                                     x_fill: true });
        this.actor.connect('destroy', this._onDestroy.bind(this));

        this.actor.connect('key-focus-in', () => {
            this._setSelected(true);
        });
        this.actor.connect('key-focus-out', () => {
            this._setSelected(false);
        });
        this.actor.connect('notify::hover', () => {
            this._setSelected(this.actor.hover);
        });

        this._userWidget = new UserWidget.UserWidget(this.user);
        layout.add(this._userWidget.actor);

        this._userWidget.actor.bind_property('label-actor', this.actor, 'label-actor',
                                             GObject.BindingFlags.SYNC_CREATE);

        this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator',
                                                 scale_x: 0,
                                                 visible: false });
        layout.add(this._timedLoginIndicator);

        this.actor.connect('clicked', this._onClicked.bind(this));
        this._onUserChanged();
    }

    _onUserChanged() {
        this._updateLoggedIn();
    }

    _updateLoggedIn() {
        if (this.user.is_logged_in())
            this.actor.add_style_pseudo_class('logged-in');
        else
            this.actor.remove_style_pseudo_class('logged-in');
    }

    _onDestroy() {
        this.user.disconnect(this._userChangedId);
    }

    _onClicked() {
        this.emit('activate');
    }

    _setSelected(selected) {
        if (selected) {
            this.actor.add_style_pseudo_class('selected');
            this.actor.grab_key_focus();
        } else {
            this.actor.remove_style_pseudo_class('selected');
        }
    }

    showTimedLoginIndicator(time) {
        let hold = new Batch.Hold();

        this.hideTimedLoginIndicator();

        this._timedLoginIndicator.visible = true;

        let startTime = GLib.get_monotonic_time();

        this._timedLoginTimeoutId = GLib.timeout_add (GLib.PRIORITY_DEFAULT, 33,
            () => {
                let currentTime = GLib.get_monotonic_time();
                let elapsedTime = (currentTime - startTime) / GLib.USEC_PER_SEC;
                this._timedLoginIndicator.scale_x = elapsedTime / time;
                if (elapsedTime >= time) {
                    this._timedLoginTimeoutId = 0;
                    hold.release();
                    return GLib.SOURCE_REMOVE;
                }

                return GLib.SOURCE_CONTINUE;
            });

        GLib.Source.set_name_by_id(this._timedLoginTimeoutId, '[gnome-shell] this._timedLoginTimeoutId');

        return hold;
    }

    hideTimedLoginIndicator() {
        if (this._timedLoginTimeoutId) {
            GLib.source_remove(this._timedLoginTimeoutId);
            this._timedLoginTimeoutId = 0;
        }

        this._timedLoginIndicator.visible = false;
        this._timedLoginIndicator.scale_x = 0.;
    }
};
Signals.addSignalMethods(UserListItem.prototype);

var UserList = class {
    constructor() {
        this.actor = new St.ScrollView({ style_class: 'login-dialog-user-list-view'});
        this.actor.set_policy(St.PolicyType.NEVER,
                              St.PolicyType.AUTOMATIC);

        this._box = new St.BoxLayout({ vertical: true,
                                       style_class: 'login-dialog-user-list',
                                       pseudo_class: 'expanded' });

        this.actor.add_actor(this._box);
        this._items = {};

        this.actor.connect('key-focus-in', this._moveFocusToItems.bind(this));
    }

    _moveFocusToItems() {
        let hasItems = Object.keys(this._items).length > 0;

        if (!hasItems)
            return;

        if (global.stage.get_key_focus() != this.actor)
            return;

        let focusSet = this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
        if (!focusSet) {
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this._moveFocusToItems();
                return false;
            });
        }
    }

    _onItemActivated(activatedItem) {
        this.emit('activate', activatedItem);
    }

    updateStyle(isExpanded) {
        let tasks = [];

        if (isExpanded)
            this._box.add_style_pseudo_class('expanded');
        else
            this._box.remove_style_pseudo_class('expanded');

        for (let userName in this._items) {
            let item = this._items[userName];
            item.actor.sync_hover();
        }
    }

    scrollToItem(item) {
        let box = item.actor.get_allocation_box();

        let adjustment = this.actor.get_vscroll_bar().get_adjustment();

        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
        Tweener.removeTweens(adjustment);
        Tweener.addTween (adjustment,
                          { value: value,
                            time: _SCROLL_ANIMATION_TIME,
                            transition: 'easeOutQuad' });
    }

    jumpToItem(item) {
        let box = item.actor.get_allocation_box();

        let adjustment = this.actor.get_vscroll_bar().get_adjustment();

        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);

        adjustment.set_value(value);
    }

    getItemFromUserName(userName) {
        let item = this._items[userName];

        if (!item)
            return null;

        return item;
    }

    containsUser(user) {
        return this._items[user.get_user_name()] != null;
    }

    addUser(user) {
        if (!user.is_loaded)
            return;

        if (user.is_system_account())
            return;

        if (user.locked)
           return;

        let userName = user.get_user_name();

        if (!userName)
            return;

        this.removeUser(user);

        let item = new UserListItem(user);
        this._box.add(item.actor, { x_fill: true });

        this._items[userName] = item;

        item.connect('activate', this._onItemActivated.bind(this));

        // Try to keep the focused item front-and-center
        item.actor.connect('key-focus-in', () => { this.scrollToItem(item); });

        this._moveFocusToItems();

        this.emit('item-added', item);
    }

    removeUser(user) {
        if (!user.is_loaded)
            return;

        let userName = user.get_user_name();

        if (!userName)
            return;

        let item = this._items[userName];

        if (!item)
            return;

        item.actor.destroy();
        delete this._items[userName];
    }

    numItems() {
        return Object.keys(this._items).length;
    }
};
Signals.addSignalMethods(UserList.prototype);

var SessionMenuButton = class {
    constructor() {
        let gearIcon = new St.Icon({ icon_name: 'emblem-system-symbolic' });
        this._button = new St.Button({ style_class: 'login-dialog-session-list-button',
                                       reactive: true,
                                       track_hover: true,
                                       can_focus: true,
                                       accessible_name: _("Choose Session"),
                                       accessible_role: Atk.Role.MENU,
                                       child: gearIcon });

        this.actor = new St.Bin({ child: this._button });

        let side = St.Side.TOP;
        let align = 0;
        if (Gdm.get_session_ids().length > _MAX_BOTTOM_MENU_ITEMS) {
            if (this.actor.text_direction == Clutter.TextDirection.RTL)
                side = St.Side.RIGHT;
            else
                side = St.Side.LEFT;
            align = 0.5;
        }
        this._menu = new PopupMenu.PopupMenu(this._button, align, side);
        Main.uiGroup.add_actor(this._menu.actor);
        this._menu.actor.hide();

        this._menu.connect('open-state-changed', (menu, isOpen) => {
             if (isOpen)
                 this._button.add_style_pseudo_class('active');
             else
                 this._button.remove_style_pseudo_class('active');
        });

        this._manager = new PopupMenu.PopupMenuManager({ actor: this._button },
                                                       { actionMode: Shell.ActionMode.NONE });
        this._manager.addMenu(this._menu);

        this._button.connect('clicked', () => { this._menu.toggle(); });

        this._items = {};
        this._activeSessionId = null;
        this._populate();
    }

    updateSensitivity(sensitive) {
        this._button.reactive = sensitive;
        this._button.can_focus = sensitive;
        this._menu.close(BoxPointer.PopupAnimation.NONE);
    }

    _updateOrnament() {
        let itemIds = Object.keys(this._items);
        for (let i = 0; i < itemIds.length; i++) {
            if (itemIds[i] == this._activeSessionId)
                this._items[itemIds[i]].setOrnament(PopupMenu.Ornament.DOT);
            else
                this._items[itemIds[i]].setOrnament(PopupMenu.Ornament.NONE);
        }
    }

    setActiveSession(sessionId) {
         if (sessionId == this._activeSessionId)
             return;

         this._activeSessionId = sessionId;
         this._updateOrnament();
    }

    close() {
        this._menu.close();
    }

    _populate() {
        let ids = Gdm.get_session_ids();
        ids.sort();

        if (ids.length <= 1) {
            this._button.hide();
            return;
        }

        for (let i = 0; i < ids.length; i++) {
            let [sessionName, sessionDescription] = Gdm.get_session_name_and_description(ids[i]);

            let id = ids[i];
            let item = new PopupMenu.PopupMenuItem(sessionName);
            this._menu.addMenuItem(item);
            this._items[id] = item;

            item.connect('activate', () => {
                this.setActiveSession(id);
                this.emit('session-activated', this._activeSessionId);
            });
        }
    }
};
Signals.addSignalMethods(SessionMenuButton.prototype);

var LoginDialog = GObject.registerClass({
    Signals: { 'failed': {} },
}, class LoginDialog extends St.Widget {
    _init(parentActor) {
        super._init({ style_class: 'login-dialog',
                      visible: false });

        this.get_accessible().set_role(Atk.Role.WINDOW);

        this.add_constraint(new Layout.MonitorConstraint({ primary: true }));
        this.connect('destroy', this._onDestroy.bind(this));
        parentActor.add_child(this);

        this._userManager = AccountsService.UserManager.get_default()
        this._gdmClient = new Gdm.Client();

        try {
            this._gdmClient.set_enabled_extensions([Gdm.UserVerifierChoiceList.interface_info().name]);
        } catch(e) {
        }

        this._settings = new Gio.Settings({ schema_id: GdmUtil.LOGIN_SCREEN_SCHEMA });

        this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_KEY,
                               this._updateBanner.bind(this));
        this._settings.connect('changed::' + GdmUtil.BANNER_MESSAGE_TEXT_KEY,
                               this._updateBanner.bind(this));
        this._settings.connect('changed::' + GdmUtil.DISABLE_USER_LIST_KEY,
                               this._updateDisableUserList.bind(this));
        this._settings.connect('changed::' + GdmUtil.LOGO_KEY,
                               this._updateLogo.bind(this));

        this._textureCache = St.TextureCache.get_default();
        this._updateLogoTextureId = this._textureCache.connect('texture-file-changed',
                                                               this._updateLogoTexture.bind(this));

        this._userSelectionBox = new St.BoxLayout({ style_class: 'login-dialog-user-selection-box',
                                                    x_align: Clutter.ActorAlign.CENTER,
                                                    y_align: Clutter.ActorAlign.CENTER,
                                                    vertical: true,
                                                    visible: false });
        this.add_child(this._userSelectionBox);

        this._userList = new UserList();
        this._userSelectionBox.add(this._userList.actor,
                                   { expand: true,
                                     x_fill: true,
                                     y_fill: true });

        this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_OR_LOG_IN);
        this._authPrompt.connect('prompted', this._onPrompted.bind(this));
        this._authPrompt.connect('reset', this._onReset.bind(this));
        this._authPrompt.hide();
        this.add_child(this._authPrompt.actor);

        // translators: this message is shown below the user list on the
        // login screen. It can be activated to reveal an entry for
        // manually entering the username.
        let notListedLabel = new St.Label({ text: _("Not listed?"),
                                            style_class: 'login-dialog-not-listed-label' });
        this._notListedButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
                                                button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                                can_focus: true,
                                                child: notListedLabel,
                                                reactive: true,
                                                x_align: St.Align.START,
                                                x_fill: true });

        this._notListedButton.connect('clicked', this._hideUserListAskForUsernameAndBeginVerification.bind(this));

        this._notListedButton.hide();

        this._userSelectionBox.add(this._notListedButton,
                                   { expand: false,
                                     x_align: St.Align.START,
                                     x_fill: true });

        this._bannerView = new St.ScrollView({ style_class: 'login-dialog-banner-view',
                                               opacity: 0,
                                               vscrollbar_policy: St.PolicyType.AUTOMATIC,
                                               hscrollbar_policy: St.PolicyType.NEVER });
        this.add_child(this._bannerView);

        let bannerBox = new St.BoxLayout({ vertical: true });

        this._bannerView.add_actor(bannerBox);
        this._bannerLabel = new St.Label({ style_class: 'login-dialog-banner',
                                           text: '' });
        this._bannerLabel.clutter_text.line_wrap = true;
        this._bannerLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        bannerBox.add_child(this._bannerLabel);
        this._updateBanner();

        this._logoBin = new St.Widget({ style_class: 'login-dialog-logo-bin',
                                        x_align: Clutter.ActorAlign.CENTER,
                                        y_align: Clutter.ActorAlign.END });
        this._logoBin.connect('resource-scale-changed', () => {
            this._updateLogoTexture(this._textureCache, this._logoFile);
        });
        this.add_child(this._logoBin);
        this._updateLogo();

        this._userList.connect('activate', (userList, item) => {
            this._onUserListActivated(item);
        });


        this._sessionMenuButton = new SessionMenuButton();
        this._sessionMenuButton.connect('session-activated',
            (list, sessionId) => {
                this._greeter.call_select_session_sync (sessionId, null);
            });
        this._sessionMenuButton.actor.opacity = 0;
        this._sessionMenuButton.actor.show();
        this._authPrompt.addActorToDefaultButtonWell(this._sessionMenuButton.actor);

        this._disableUserList = undefined;
        this._userListLoaded = false;

        this._realmManager = new Realmd.Manager();
        this._realmSignalId = this._realmManager.connect('login-format-changed',
                                                         this._showRealmLoginHint.bind(this));

        LoginManager.getLoginManager().getCurrentSessionProxy(this._gotGreeterSessionProxy.bind(this));

        // If the user list is enabled, it should take key focus; make sure the
        // screen shield is initialized first to prevent it from stealing the
        // focus later
        this._startupCompleteId = Main.layoutManager.connect('startup-complete',
                                                             this._updateDisableUserList.bind(this));
    }

    _getBannerAllocation(dialogBox) {
        let actorBox = new Clutter.ActorBox();

        let [minWidth, minHeight, natWidth, natHeight] = this._bannerView.get_preferred_size();
        let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2;

        actorBox.x1 = Math.floor(centerX - natWidth / 2);
        actorBox.y1 = dialogBox.y1 + Main.layoutManager.panelBox.height;
        actorBox.x2 = actorBox.x1 + natWidth;
        actorBox.y2 = actorBox.y1 + natHeight;

        return actorBox;
    }

    _getLogoBinAllocation(dialogBox) {
        let actorBox = new Clutter.ActorBox();

        let [minWidth, minHeight, natWidth, natHeight] = this._logoBin.get_preferred_size();
        let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2;

        actorBox.x1 = Math.floor(centerX - natWidth / 2);
        actorBox.y1 = dialogBox.y2 - natHeight;
        actorBox.x2 = actorBox.x1 + natWidth;
        actorBox.y2 = actorBox.y1 + natHeight;

        return actorBox;
    }

    _getCenterActorAllocation(dialogBox, actor) {
        let actorBox = new Clutter.ActorBox();

        let [minWidth, minHeight, natWidth, natHeight] = actor.get_preferred_size();
        let centerX = dialogBox.x1 + (dialogBox.x2 - dialogBox.x1) / 2;
        let centerY = dialogBox.y1 + (dialogBox.y2 - dialogBox.y1) / 2;

        natWidth = Math.min(natWidth, dialogBox.x2 - dialogBox.x1);
        natHeight = Math.min(natHeight, dialogBox.y2 - dialogBox.y1);

        actorBox.x1 = Math.floor(centerX - natWidth / 2);
        actorBox.y1 = Math.floor(centerY - natHeight / 2);
        actorBox.x2 = actorBox.x1 + natWidth;
        actorBox.y2 = actorBox.y1 + natHeight;

        return actorBox;
    }

    vfunc_allocate(dialogBox, flags) {
        this.set_allocation(dialogBox, flags);

        let themeNode = this.get_theme_node();
        dialogBox = themeNode.get_content_box(dialogBox);

        let dialogWidth = dialogBox.x2 - dialogBox.x1;
        let dialogHeight = dialogBox.y2 - dialogBox.y1;

        // First find out what space the children require
        let bannerAllocation = null;
        let bannerHeight = 0;
        let bannerWidth = 0;
        if (this._bannerView.visible) {
            bannerAllocation = this._getBannerAllocation(dialogBox, this._bannerView);
            bannerHeight = bannerAllocation.y2 - bannerAllocation.y1;
            bannerWidth = bannerAllocation.x2 - bannerAllocation.x1;
        }

        let authPromptAllocation = null;
        let authPromptHeight = 0;
        let authPromptWidth = 0;
        if (this._authPrompt.actor.visible) {
            authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt.actor);
            authPromptHeight = authPromptAllocation.y2 - authPromptAllocation.y1;
            authPromptWidth = authPromptAllocation.x2 - authPromptAllocation.x1;
        }

        let userSelectionAllocation = null;
        let userSelectionHeight = 0;
        if (this._userSelectionBox.visible) {
            userSelectionAllocation = this._getCenterActorAllocation(dialogBox, this._userSelectionBox);
            userSelectionHeight = userSelectionAllocation.y2 - userSelectionAllocation.y1;
        }

        let logoAllocation = null;
        let logoHeight = 0;
        if (this._logoBin.visible) {
            logoAllocation = this._getLogoBinAllocation(dialogBox);
            logoHeight = logoAllocation.y2 - logoAllocation.y1;
        }

        // Then figure out if we're overly constrained and need to
        // try a different layout, or if we have what extra space we
        // can hand out
        if (bannerAllocation) {
            let bannerSpace;

            if (authPromptAllocation)
                bannerSpace = authPromptAllocation.y1 - bannerAllocation.y1;
            else
                bannerSpace = 0;

            let leftOverYSpace = bannerSpace - bannerHeight;

            if (leftOverYSpace > 0) {
                 // First figure out how much left over space is up top
                 let leftOverTopSpace = leftOverYSpace / 2;

                 // Then, shift the banner into the middle of that extra space
                 let yShift = Math.floor(leftOverTopSpace / 2);

                 bannerAllocation.y1 += yShift;
                 bannerAllocation.y2 += yShift;
            } else {
                 // Then figure out how much space there would be if we switched to a
                 // wide layout with banner on one side and authprompt on the other.
                 let leftOverXSpace = dialogWidth - authPromptWidth;

                 // In a wide view, half of the available space goes to the banner,
                 // and the other half goes to the margins.
                 let wideBannerWidth = leftOverXSpace / 2;
                 let wideSpacing  = leftOverXSpace - wideBannerWidth;

                 // If we do go with a wide layout, we need there to be at least enough
                 // space for the banner and the auth prompt to be the same width,
                 // so it doesn't look unbalanced.
                 if (authPromptWidth > 0 && wideBannerWidth > authPromptWidth) {
                     let centerX = dialogBox.x1 + dialogWidth / 2;
                     let centerY = dialogBox.y1 + dialogHeight / 2;

                     // A small portion of the spacing goes down the center of the
                     // screen to help delimit the two columns of the wide view
                     let centerGap = wideSpacing / 8;

                     // place the banner along the left edge of the center margin
                     bannerAllocation.x2 = Math.floor(centerX - centerGap / 2);
                     bannerAllocation.x1 = Math.floor(bannerAllocation.x2 - wideBannerWidth);

                     // figure out how tall it would like to be and try to accomodate
                     // but don't let it get too close to the logo
                     let [wideMinHeight, wideBannerHeight] = this._bannerView.get_preferred_height(wideBannerWidth);

                     let maxWideHeight = dialogHeight - 3 * logoHeight;
                     wideBannerHeight = Math.min(maxWideHeight, wideBannerHeight);
                     bannerAllocation.y1 = Math.floor(centerY - wideBannerHeight / 2);
                     bannerAllocation.y2 = bannerAllocation.y1 + wideBannerHeight;

                     // place the auth prompt along the right edge of the center margin
                     authPromptAllocation.x1 = Math.floor(centerX + centerGap / 2);
                     authPromptAllocation.x2 = authPromptAllocation.x1 + authPromptWidth;
                 } else {
                     // If we aren't going to do a wide view, then we need to limit
                     // the height of the banner so it will present scrollbars

                     // First figure out how much space there is without the banner
                     leftOverYSpace += bannerHeight;

                     // Then figure out how much of that space is up top
                     let availableTopSpace = Math.floor(leftOverYSpace / 2);

                     // Then give all of that space to the banner
                     bannerAllocation.y2 = bannerAllocation.y1 + availableTopSpace;
                 }
            }
        } else if (userSelectionAllocation) {
            // Grow the user list to fill the space
            let leftOverYSpace = dialogHeight - userSelectionHeight - logoHeight;

            if (leftOverYSpace > 0) {
                let topExpansion = Math.floor(leftOverYSpace / 2);
                let bottomExpansion = topExpansion;

                userSelectionAllocation.y1 -= topExpansion;
                userSelectionAllocation.y2 += bottomExpansion;
            }
        }

        // Finally hand out the allocations
        if (bannerAllocation) {
            this._bannerView.allocate(bannerAllocation, flags);
        }

        if (authPromptAllocation)
            this._authPrompt.actor.allocate(authPromptAllocation, flags);

        if (userSelectionAllocation)
            this._userSelectionBox.allocate(userSelectionAllocation, flags);

        if (logoAllocation)
            this._logoBin.allocate(logoAllocation, flags);
    }

    _ensureUserListLoaded() {
        if (!this._userManager.is_loaded) {
            this._userManagerLoadedId = this._userManager.connect('notify::is-loaded',
                () => {
                    if (this._userManager.is_loaded) {
                        this._loadUserList();
                        this._userManager.disconnect(this._userManagerLoadedId);
                        this._userManagerLoadedId = 0;
                    }
                });
        } else {
            let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, this._loadUserList.bind(this));
            GLib.Source.set_name_by_id(id, '[gnome-shell] _loadUserList');
        }
    }

    _updateDisableUserList() {
        let disableUserList = this._settings.get_boolean(GdmUtil.DISABLE_USER_LIST_KEY);

        // Disable user list when there are no users.
        if (this._userListLoaded && this._userList.numItems() == 0)
            disableUserList = true;

        if (disableUserList != this._disableUserList) {
            this._disableUserList = disableUserList;

            if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING)
                this._authPrompt.reset();

            if (this._disableUserList && this._timedLoginUserListHold)
                this._timedLoginUserListHold.release();
        }
    }

    _updateCancelButton() {
        let cancelVisible;

        // Hide the cancel button if the user list is disabled and we're asking for
        // a username
        if (this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING && this._disableUserList)
            cancelVisible = false;
        else
            cancelVisible = true;

        this._authPrompt.cancelButton.visible = cancelVisible;
    }

    _updateBanner() {
        let enabled = this._settings.get_boolean(GdmUtil.BANNER_MESSAGE_KEY);
        let text = this._settings.get_string(GdmUtil.BANNER_MESSAGE_TEXT_KEY);

        if (enabled && text) {
            this._bannerLabel.set_text(text);
            this._bannerLabel.show();
        } else {
            this._bannerLabel.hide();
        }
    }

    _fadeInBannerView() {
        this._bannerView.show();
        Tweener.addTween(this._bannerView,
                         { opacity: 255,
                           time: _FADE_ANIMATION_TIME,
                           transition: 'easeOutQuad' });
    }

    _hideBannerView() {
        Tweener.removeTweens(this._bannerView);
        this._bannerView.opacity = 0;
        this._bannerView.hide();
    }

    _updateLogoTexture(cache, file) {
        if (this._logoFile && !this._logoFile.equal(file))
            return;

        this._logoBin.destroy_all_children();
        if (this._logoFile && this._logoBin.resource_scale > 0) {
            let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
            this._logoBin.add_child(this._textureCache.load_file_async(this._logoFile,
                                                                       -1, _LOGO_ICON_HEIGHT,
                                                                       scaleFactor,
                                                                       this._logoBin.resource_scale));
        }
    }

    _updateLogo() {
        let path = this._settings.get_string(GdmUtil.LOGO_KEY);

        this._logoFile = path ? Gio.file_new_for_path(path) : null;
        this._updateLogoTexture(this._textureCache, this._logoFile);
    }

    _onPrompted() {
        if (this._shouldShowSessionMenuButton()) {
            this._sessionMenuButton.updateSensitivity(true);
            this._authPrompt.setActorInDefaultButtonWell(this._sessionMenuButton.actor);
        } else {
            this._sessionMenuButton.updateSensitivity(false);
        }
        this._showPrompt();
    }

    _resetGreeterProxy() {
        if (GLib.getenv('GDM_GREETER_TEST') != '1') {
            if (this._greeter) {
                this._greeter.run_dispose();
            }
            this._greeter = this._gdmClient.get_greeter_sync(null);

            this._defaultSessionChangedId = this._greeter.connect('default-session-name-changed',
                                                                  this._onDefaultSessionChanged.bind(this));
            this._sessionOpenedId = this._greeter.connect('session-opened',
                                                          this._onSessionOpened.bind(this));
            this._timedLoginRequestedId = this._greeter.connect('timed-login-requested',
                                                                this._onTimedLoginRequested.bind(this));
        }
    }

    _onReset(authPrompt, beginRequest) {
        this._resetGreeterProxy();
        this._sessionMenuButton.updateSensitivity(true);

        this._user = null;

        if (this._nextSignalId) {
            this._authPrompt.disconnect(this._nextSignalId);
            this._nextSignalId = 0;
        }

        if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) {
            if (!this._disableUserList)
                this._showUserList();
            else
                this._hideUserListAskForUsernameAndBeginVerification();
        } else {
            this._hideUserListAndBeginVerification();
        }
    }

    _onDefaultSessionChanged(client, sessionId) {
        this._sessionMenuButton.setActiveSession(sessionId);
    }

    _shouldShowSessionMenuButton() {
        if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFYING &&
            this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.VERIFICATION_FAILED)
          return false;

        if (this._user && this._user.is_loaded && this._user.is_logged_in())
          return false;

        return true;
    }

    _showPrompt() {
        if (this._authPrompt.actor.visible)
            return;
        this._authPrompt.actor.opacity = 0;
        this._authPrompt.actor.show();
        Tweener.addTween(this._authPrompt.actor,
                         { opacity: 255,
                           time: _FADE_ANIMATION_TIME,
                           transition: 'easeOutQuad' });
        this._fadeInBannerView();
    }

    _showRealmLoginHint(realmManager, hint) {
        if (!hint)
            return;

        hint = hint.replace(/%U/g, 'user');
        hint = hint.replace(/%D/g, 'DOMAIN');
        hint = hint.replace(/%[^UD]/g, '');

        // Translators: this message is shown below the username entry field
        // to clue the user in on how to login to the local network realm
        this._authPrompt.setMessage(_("(e.g., user or %s)").format(hint), GdmUtil.MessageType.HINT);
    }

    _askForUsernameAndBeginVerification() {
        this._authPrompt.setPasswordChar('');
        this._authPrompt.setQuestion(_("Username: "));

        this._showRealmLoginHint(this._realmManager.loginFormat);

        if (this._nextSignalId)
            this._authPrompt.disconnect(this._nextSignalId);
        this._nextSignalId = this._authPrompt.connect('next',
            () => {
                this._authPrompt.disconnect(this._nextSignalId);
                this._nextSignalId = 0;
                this._authPrompt.updateSensitivity(false);
                let answer = this._authPrompt.getAnswer();
                this._user = this._userManager.get_user(answer);
                this._authPrompt.clear();
                this._authPrompt.startSpinning();
                this._authPrompt.begin({ userName: answer });
                this._updateCancelButton();
            });
        this._updateCancelButton();

        this._sessionMenuButton.updateSensitivity(false);
        this._authPrompt.updateSensitivity(true);
        this._showPrompt();
    }

    _loginScreenSessionActivated() {
        if (this.opacity == 255 && this._authPrompt.verificationStatus == AuthPrompt.AuthPromptStatus.NOT_VERIFYING)
            return;

        if (this._authPrompt.verificationStatus != AuthPrompt.AuthPromptStatus.NOT_VERIFYING)
            this._authPrompt.reset();

        Tweener.addTween(this,
                         { opacity: 255,
                           time: _FADE_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onUpdate() {
                               let children = Main.layoutManager.uiGroup.get_children();

                               for (let i = 0; i < children.length; i++) {
                                   if (children[i] != Main.layoutManager.screenShieldGroup)
                                       children[i].opacity = this.opacity;
                               }
                           },
                           onUpdateScope: this });
    }

    _gotGreeterSessionProxy(proxy) {
        this._greeterSessionProxy = proxy;
        this._greeterSessionProxyChangedId =
            proxy.connect('g-properties-changed', () => {
                if (proxy.Active)
                    this._loginScreenSessionActivated();
            });
    }

    _startSession(serviceName) {
        Tweener.addTween(this,
                         { opacity: 0,
                           time: _FADE_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onUpdate() {
                               let children = Main.layoutManager.uiGroup.get_children();

                               for (let i = 0; i < children.length; i++) {
                                   if (children[i] != Main.layoutManager.screenShieldGroup)
                                       children[i].opacity = this.opacity;
                               }
                           },
                           onUpdateScope: this,
                           onComplete() {
                               this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
                           },
                           onCompleteScope: this });
    }

    _onSessionOpened(client, serviceName) {
        this._authPrompt.finish(() => { this._startSession(serviceName); });
    }

    _waitForItemForUser(userName) {
        let item = this._userList.getItemFromUserName(userName);

        if (item)
          return null;

        let hold = new Batch.Hold();
        let signalId = this._userList.connect('item-added',
            () => {
                let item = this._userList.getItemFromUserName(userName);

                if (item)
                    hold.release();
            });

        hold.connect('release', () => { this._userList.disconnect(signalId); });

        return hold;
    }

    _blockTimedLoginUntilIdle() {
        let hold = new Batch.Hold();

        this._timedLoginIdleTimeOutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, _TIMED_LOGIN_IDLE_THRESHOLD,
            () => {
                this._timedLoginIdleTimeOutId = 0;
                hold.release();
                return GLib.SOURCE_REMOVE;
            });
        GLib.Source.set_name_by_id(this._timedLoginIdleTimeOutId, '[gnome-shell] this._timedLoginIdleTimeOutId');
        return hold;
    }

    _startTimedLogin(userName, delay) {
        let firstRun = true;

        // Cancel execution of old batch
        if (this._timedLoginBatch) {
            this._timedLoginBatch.cancel();
            this._timedLoginBatch = null;
            firstRun = false;
        }

        // Reset previous idle-timeout
        if (this._timedLoginIdleTimeOutId) {
            GLib.source_remove(this._timedLoginIdleTimeOutId);
            this._timedLoginIdleTimeOutId = 0;
        }

        let loginItem = null;
        let animationTime;

        let tasks = [() => {
                         if (this._disableUserList)
                             return;

                         this._timedLoginUserListHold = this._waitForItemForUser(userName);

                         return this._timedLoginUserListHold;
                     },

                     () => {
                         this._timedLoginUserListHold = null;


                         loginItem = this._disableUserList
                            ? this._authPrompt
                            : this._userList.getItemFromUserName(userName);

                         // If there is an animation running on the item, reset it.
                         loginItem.hideTimedLoginIndicator();
                     },

                     () => {
                         if (this._disableUserList)
                             return;

                         // If we're just starting out, start on the right item.
                         if (!this._userManager.is_loaded) {
                             this._userList.jumpToItem(loginItem);
                         }
                     },

                     () => {
                         // This blocks the timed login animation until a few
                         // seconds after the user stops interacting with the
                         // login screen.

                         // We skip this step if the timed login delay is very short.
                         if (delay > _TIMED_LOGIN_IDLE_THRESHOLD) {
                             animationTime = delay - _TIMED_LOGIN_IDLE_THRESHOLD;
                             return this._blockTimedLoginUntilIdle();
                         } else {
                             animationTime = delay;
                         }
                     },

                     () => {
                         if (this._disableUserList)
                             return;

                         // If idle timeout is done, make sure the timed login indicator is shown
                         if (delay > _TIMED_LOGIN_IDLE_THRESHOLD &&
                             this._authPrompt.actor.visible)
                             this._authPrompt.cancel();

                         if (delay > _TIMED_LOGIN_IDLE_THRESHOLD || firstRun) {
                             this._userList.scrollToItem(loginItem);
                             loginItem.actor.grab_key_focus();
                         }
                     },

                     () => loginItem.showTimedLoginIndicator(animationTime),

                     () => {
                         this._timedLoginBatch = null;
                         this._greeter.call_begin_auto_login_sync(userName, null);
                     }];

        this._timedLoginBatch = new Batch.ConsecutiveBatch(this, tasks);

        return this._timedLoginBatch.run();
    }

    _onTimedLoginRequested(client, userName, seconds) {
        if (this._timedLoginBatch)
            return;

        this._startTimedLogin(userName, seconds);

        // Restart timed login on user interaction
        global.stage.connect('captured-event', (actor, event) => {
           if (event.type() == Clutter.EventType.KEY_PRESS ||
               event.type() == Clutter.EventType.BUTTON_PRESS) {
               this._startTimedLogin(userName, seconds);
           }

           return Clutter.EVENT_PROPAGATE;
        });
    }

    _setUserListExpanded(expanded) {
        this._userList.updateStyle(expanded);
        this._userSelectionBox.visible = expanded;
    }

    _hideUserList() {
        this._setUserListExpanded(false);
        if (this._userSelectionBox.visible)
            GdmUtil.cloneAndFadeOutActor(this._userSelectionBox);
    }

    _hideUserListAskForUsernameAndBeginVerification() {
        this._hideUserList();
        this._askForUsernameAndBeginVerification();
    }

    _hideUserListAndBeginVerification() {
        this._hideUserList();
        this._authPrompt.begin();
    }

    _showUserList() {
        this._ensureUserListLoaded();
        this._authPrompt.hide();
        this._hideBannerView();
        this._sessionMenuButton.close();
        this._setUserListExpanded(true);
        this._notListedButton.show();
        this._userList.actor.grab_key_focus();
    }

    _beginVerificationForItem(item) {
        this._authPrompt.setUser(item.user);

        let userName = item.user.get_user_name();
        let hold = new Batch.Hold();

        this._authPrompt.begin({ userName: userName,
                                 hold: hold });
        return hold;
    }

    _onUserListActivated(activatedItem) {
        this._user = activatedItem.user;

        this._updateCancelButton();

        let batch = new Batch.ConcurrentBatch(this, [GdmUtil.cloneAndFadeOutActor(this._userSelectionBox),
                                                     this._beginVerificationForItem(activatedItem)]);
        batch.run();
    }

    _onDestroy() {
        if (this._userManagerLoadedId) {
            this._userManager.disconnect(this._userManagerLoadedId);
            this._userManagerLoadedId = 0;
        }
        if (this._userAddedId) {
            this._userManager.disconnect(this._userAddedId);
            this._userAddedId = 0;
        }
        if (this._userRemovedId) {
            this._userManager.disconnect(this._userRemovedId);
            this._userRemovedId = 0;
        }
        if (this._userChangedId) {
            this._userManager.disconnect(this._userChangedId);
            this._userChangedId = 0;
        }
        this._textureCache.disconnect(this._updateLogoTextureId);
        Main.layoutManager.disconnect(this._startupCompleteId);
        if (this._settings) {
            this._settings.run_dispose();
            this._settings = null;
        }
        if (this._greeter) {
            this._greeter.disconnect(this._defaultSessionChangedId);
            this._greeter.disconnect(this._sessionOpenedId);
            this._greeter.disconnect(this._timedLoginRequestedId);
            this._greeter = null;
        }
        if (this._greeterSessionProxy) {
            this._greeterSessionProxy.disconnect(this._greeterSessionProxyChangedId);
            this._greeterSessionProxy = null;
        }
        if (this._realmManager) {
            this._realmManager.disconnect(this._realmSignalId);
            this._realmSignalId = 0;
            this._realmManager.release();
            this._realmManager = null;
        }
    }

    _loadUserList() {
        if (this._userListLoaded)
            return GLib.SOURCE_REMOVE;

        this._userListLoaded = true;

        let users = this._userManager.list_users();

        for (let i = 0; i < users.length; i++) {
            this._userList.addUser(users[i]);
        }

        this._updateDisableUserList();

        this._userAddedId = this._userManager.connect('user-added',
            (userManager, user) => {
                this._userList.addUser(user);
                this._updateDisableUserList();
            });

        this._userRemovedId = this._userManager.connect('user-removed',
            (userManager, user) => {
                this._userList.removeUser(user);
                this._updateDisableUserList();
            });

        this._userChangedId = this._userManager.connect('user-changed',
            (userManager, user) => {
                if (this._userList.containsUser(user) && user.locked)
                    this._userList.removeUser(user);
                else if (!this._userList.containsUser(user) && !user.locked)
                    this._userList.addUser(user);
                this._updateDisableUserList();
            });

        return GLib.SOURCE_REMOVE;
    }

    open() {
        Main.ctrlAltTabManager.addGroup(this,
                                        _("Login Window"),
                                        'dialog-password-symbolic',
                                        { sortGroup: CtrlAltTab.SortGroup.MIDDLE });
        this._userList.actor.grab_key_focus();
        this.show();
        this.opacity = 0;

        Main.pushModal(this, { actionMode: Shell.ActionMode.LOGIN_SCREEN });

        Tweener.addTween(this,
                         { opacity: 255,
                           time: 1,
                           transition: 'easeInQuad' });

        return true;
    }

    close() {
        Main.popModal(this);
        Main.ctrlAltTabManager.removeGroup(this);
    }

    cancel() {
        this._authPrompt.cancel();
    }

    addCharacter(unichar) {
        // Don't allow type ahead at the login screen
    }

    finish(onComplete) {
        this._authPrompt.finish(onComplete);
    }
});
(uuay)panel.js^�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Cairo = imports.cairo;
const Mainloop = imports.mainloop;

const Animation = imports.ui.animation;
const Config = imports.misc.config;
const CtrlAltTab = imports.ui.ctrlAltTab;
const DND = imports.ui.dnd;
const Overview = imports.ui.overview;
const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var PANEL_ICON_SIZE = 16;
var APP_MENU_ICON_MARGIN = 0;

var BUTTON_DND_ACTIVATION_TIMEOUT = 250;

var SPINNER_ANIMATION_TIME = 1.0;

// To make sure the panel corners blend nicely with the panel,
// we draw background and borders the same way, e.g. drawing
// them as filled shapes from the outside inwards instead of
// using cairo stroke(). So in order to give the border the
// appearance of being drawn on top of the background, we need
// to blend border and background color together.
// For that purpose we use the following helper methods, taken
// from st-theme-node-drawing.c
function _norm(x) {
    return Math.round(x / 255);
}

function _over(srcColor, dstColor) {
    let src = _premultiply(srcColor);
    let dst = _premultiply(dstColor);
    let result = new Clutter.Color();

    result.alpha = src.alpha + _norm((255 - src.alpha) * dst.alpha);
    result.red = src.red + _norm((255 - src.alpha) * dst.red);
    result.green = src.green + _norm((255 - src.alpha) * dst.green);
    result.blue = src.blue + _norm((255 - src.alpha) * dst.blue);

    return _unpremultiply(result);
}

function _premultiply(color) {
    return new Clutter.Color({ red: _norm(color.red * color.alpha),
                               green: _norm(color.green * color.alpha),
                               blue: _norm(color.blue * color.alpha),
                               alpha: color.alpha });
};

function _unpremultiply(color) {
    if (color.alpha == 0)
        return new Clutter.Color();

    let red = Math.min((color.red * 255 + 127) / color.alpha, 255);
    let green = Math.min((color.green * 255 + 127) / color.alpha, 255);
    let blue = Math.min((color.blue * 255 + 127) / color.alpha, 255);
    return new Clutter.Color({ red: red, green: green,
                               blue: blue, alpha: color.alpha });
};

class AppMenu extends PopupMenu.PopupMenu {
    constructor(sourceActor) {
        super(sourceActor, 0.0, St.Side.TOP);

        this.actor.add_style_class_name('app-menu');

        this._app = null;
        this._appSystem = Shell.AppSystem.get_default();

        this._windowsChangedId = 0;

        this._windowSection = new PopupMenu.PopupMenuSection();
        this.addMenuItem(this._windowSection);

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

        this._newWindowItem = this.addAction(_("New Window"), () => {
            this._app.open_new_window(-1);
        });

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

        this._actionSection = new PopupMenu.PopupMenuSection();
        this.addMenuItem(this._actionSection);

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

        this._detailsItem = this.addAction(_("Show Details"), () => {
            let id = this._app.get_id();
            let args = GLib.Variant.new('(ss)', [id, '']);
            Gio.DBus.get(Gio.BusType.SESSION, null, (o, res) => {
                let bus = Gio.DBus.get_finish(res);
                bus.call('org.gnome.Software',
                         '/org/gnome/Software',
                         'org.gtk.Actions', 'Activate',
                         GLib.Variant.new('(sava{sv})',
                                          ['details', [args], null]),
                         null, 0, -1, null, null);
            });
        });

        this.addAction(_("Quit"), () => {
            this._app.request_quit();
        });

        this._appSystem.connect('installed-changed', () => {
            this._updateDetailsVisibility();
        });
        this._updateDetailsVisibility();
    }

    _updateDetailsVisibility() {
        let sw = this._appSystem.lookup_app('org.gnome.Software.desktop');
        this._detailsItem.actor.visible = (sw != null);
    }

    isEmpty() {
        if (!this._app)
            return true;
        return super.isEmpty();
    }

    setApp(app) {
        if (this._app == app)
            return;

        if (this._windowsChangedId)
            this._app.disconnect(this._windowsChangedId);
        this._windowsChangedId = 0;

        this._app = app;

        if (app) {
            this._windowsChangedId = app.connect('windows-changed', () => {
                this._updateWindowsSection();
            });
        }

        this._updateWindowsSection();

        let appInfo = app ? app.app_info : null;
        let actions = appInfo ? appInfo.list_actions() : [];

        this._actionSection.removeAll();
        actions.forEach(action => {
            let label = appInfo.get_action_name(action);
            this._actionSection.addAction(label, event => {
                this._app.launch_action(action, event.get_time(), -1);
            });
        });

        this._newWindowItem.actor.visible =
            app && app.can_open_new_window() && !actions.includes('new-window');
    }

    _updateWindowsSection() {
        this._windowSection.removeAll();

        if (!this._app)
            return;

        let windows = this._app.get_windows();
        windows.forEach(window => {
            let title = window.title || this._app.get_name();
            this._windowSection.addAction(title, event => {
                Main.activateWindow(window, event.get_time());
            });
        });

        // Add separator between windows of the current desktop and other windows.
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        let pos = windows.findIndex(w => w.get_workspace() != activeWorkspace);
        if (pos >= 0)
            this._windowSection.addMenuItem(new PopupMenu.PopupSeparatorMenuItem(), pos);
    }
}

/**
 * AppMenuButton:
 *
 * This class manages the "application menu" component.  It tracks the
 * currently focused application.  However, when an app is launched,
 * this menu also handles startup notification for it.  So when we
 * have an active startup notification, we switch modes to display that.
 */
var AppMenuButton = GObject.registerClass({
    Signals: {'changed': {}},
}, class AppMenuButton extends PanelMenu.Button {
    _init(panel) {
        super._init(0.0, null, true);

        this.actor.accessible_role = Atk.Role.MENU;

        this._startingApps = [];

        this._menuManager = panel.menuManager;
        this._targetApp = null;
        this._busyNotifyId = 0;

        let bin = new St.Bin({ name: 'appMenu' });
        bin.connect('style-changed', this._onStyleChanged.bind(this));
        this.actor.add_actor(bin);

        this.actor.bind_property("reactive", this.actor, "can-focus", 0);
        this.actor.reactive = false;

        this._container = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
        bin.set_child(this._container);

        let textureCache = St.TextureCache.get_default();
        textureCache.connect('icon-theme-changed',
                             this._onIconThemeChanged.bind(this));

        let iconEffect = new Clutter.DesaturateEffect();
        this._iconBox = new St.Bin({ style_class: 'app-menu-icon' });
        this._iconBox.add_effect(iconEffect);
        this._container.add_actor(this._iconBox);

        this._iconBox.connect('style-changed', () => {
            let themeNode = this._iconBox.get_theme_node();
            iconEffect.enabled = themeNode.get_icon_style() == St.IconStyle.SYMBOLIC;
        });

        this._label = new St.Label({ y_expand: true,
                                     y_align: Clutter.ActorAlign.CENTER });
        this._container.add_actor(this._label);
        this._arrow = PopupMenu.arrowIcon(St.Side.BOTTOM);
        this._container.add_actor(this._arrow);

        this._visible = !Main.overview.visible;
        if (!this._visible)
            this.hide();
        this._overviewHidingId = Main.overview.connect('hiding', this._sync.bind(this));
        this._overviewShowingId = Main.overview.connect('showing', this._sync.bind(this));

        this._stop = true;

        this._spinner = null;

        let menu = new AppMenu(this);
        this.setMenu(menu);
        this._menuManager.addMenu(menu);

        let tracker = Shell.WindowTracker.get_default();
        let appSys = Shell.AppSystem.get_default();
        this._focusAppNotifyId =
            tracker.connect('notify::focus-app', this._focusAppChanged.bind(this));
        this._appStateChangedSignalId =
            appSys.connect('app-state-changed', this._onAppStateChanged.bind(this));
        this._switchWorkspaceNotifyId =
            global.window_manager.connect('switch-workspace', this._sync.bind(this));

        this._sync();
    }

    fadeIn() {
        if (this._visible)
            return;

        this._visible = true;
        this.actor.reactive = true;
        this.show();
        Tweener.removeTweens(this.actor);
        Tweener.addTween(this.actor,
                         { opacity: 255,
                           time: Overview.ANIMATION_TIME,
                           transition: 'easeOutQuad' });
    }

    fadeOut() {
        if (!this._visible)
            return;

        this._visible = false;
        this.actor.reactive = false;
        Tweener.removeTweens(this.actor);
        Tweener.addTween(this.actor,
                         { opacity: 0,
                           time: Overview.ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete() {
                               this.hide();
                           },
                           onCompleteScope: this });
    }

    _onStyleChanged(actor) {
        let node = actor.get_theme_node();
        let [success, icon] = node.lookup_url('spinner-image', false);
        if (!success || (this._spinnerIcon && this._spinnerIcon.equal(icon)))
            return;
        this._spinnerIcon = icon;
        this._spinner = new Animation.AnimatedIcon(this._spinnerIcon, PANEL_ICON_SIZE);
        this._container.add_actor(this._spinner.actor);
        this._spinner.actor.hide();
    }

    _syncIcon() {
        if (!this._targetApp)
            return;

        let icon = this._targetApp.create_icon_texture(PANEL_ICON_SIZE - APP_MENU_ICON_MARGIN);
        this._iconBox.set_child(icon);
    }

    _onIconThemeChanged() {
        if (this._iconBox.child == null)
            return;

        this._syncIcon();
    }

    stopAnimation() {
        if (this._stop)
            return;

        this._stop = true;

        if (this._spinner == null)
            return;

        Tweener.addTween(this._spinner.actor,
                         { opacity: 0,
                           time: SPINNER_ANIMATION_TIME,
                           transition: "easeOutQuad",
                           onCompleteScope: this,
                           onComplete() {
                               this._spinner.stop();
                               this._spinner.actor.opacity = 255;
                               this._spinner.actor.hide();
                           }
                         });
    }

    startAnimation() {
        this._stop = false;

        if (this._spinner == null)
            return;

        this._spinner.play();
        this._spinner.actor.show();
    }

    _onAppStateChanged(appSys, app) {
        let state = app.state;
        if (state != Shell.AppState.STARTING)
            this._startingApps = this._startingApps.filter(a => a != app);
        else if (state == Shell.AppState.STARTING)
            this._startingApps.push(app);
        // For now just resync on all running state changes; this is mainly to handle
        // cases where the focused window's application changes without the focus
        // changing.  An example case is how we map OpenOffice.org based on the window
        // title which is a dynamic property.
        this._sync();
    }

    _focusAppChanged() {
        let tracker = Shell.WindowTracker.get_default();
        let focusedApp = tracker.focus_app;
        if (!focusedApp) {
            // If the app has just lost focus to the panel, pretend
            // nothing happened; otherwise you can't keynav to the
            // app menu.
            if (global.stage.key_focus != null)
                return;
        }
        this._sync();
    }

    _findTargetApp() {
        let workspaceManager = global.workspace_manager;
        let workspace = workspaceManager.get_active_workspace();
        let tracker = Shell.WindowTracker.get_default();
        let focusedApp = tracker.focus_app;
        if (focusedApp && focusedApp.is_on_workspace(workspace))
            return focusedApp;

        for (let i = 0; i < this._startingApps.length; i++)
            if (this._startingApps[i].is_on_workspace(workspace))
                return this._startingApps[i];

        return null;
    }

    _sync() {
        let targetApp = this._findTargetApp();

        if (this._targetApp != targetApp) {
            if (this._busyNotifyId) {
                this._targetApp.disconnect(this._busyNotifyId);
                this._busyNotifyId = 0;
            }

            this._targetApp = targetApp;

            if (this._targetApp) {
                this._busyNotifyId = this._targetApp.connect('notify::busy', this._sync.bind(this));
                this._label.set_text(this._targetApp.get_name());
                this.actor.set_accessible_name(this._targetApp.get_name());
            }
        }

        let visible = (this._targetApp != null && !Main.overview.visibleTarget);
        if (visible)
            this.fadeIn();
        else
            this.fadeOut();

        let isBusy = (this._targetApp != null &&
                      (this._targetApp.get_state() == Shell.AppState.STARTING ||
                       this._targetApp.get_busy()));
        if (isBusy)
            this.startAnimation();
        else
            this.stopAnimation();

        this.actor.reactive = (visible && !isBusy);

        this._syncIcon();
        this.menu.setApp(this._targetApp);
        this.emit('changed');
    }

    _onDestroy() {
        if (this._appStateChangedSignalId > 0) {
            let appSys = Shell.AppSystem.get_default();
            appSys.disconnect(this._appStateChangedSignalId);
            this._appStateChangedSignalId = 0;
        }
        if (this._focusAppNotifyId > 0) {
            let tracker = Shell.WindowTracker.get_default();
            tracker.disconnect(this._focusAppNotifyId);
            this._focusAppNotifyId = 0;
        }
        if (this._overviewHidingId > 0) {
            Main.overview.disconnect(this._overviewHidingId);
            this._overviewHidingId = 0;
        }
        if (this._overviewShowingId > 0) {
            Main.overview.disconnect(this._overviewShowingId);
            this._overviewShowingId = 0;
        }
        if (this._switchWorkspaceNotifyId > 0) {
            global.window_manager.disconnect(this._switchWorkspaceNotifyId);
            this._switchWorkspaceNotifyId = 0;
        }

        super._onDestroy();
    }
});

var ActivitiesButton = GObject.registerClass(
class ActivitiesButton extends PanelMenu.Button {
    _init() {
        super._init(0.0, null, true);
        this.actor.accessible_role = Atk.Role.TOGGLE_BUTTON;

        this.actor.name = 'panelActivities';

        let box = new St.BoxLayout();
        this.actor.add_actor(box);
        let iconFile = Gio.File.new_for_path('/usr/share/icons/hicolor/scalable/apps/start-here.svg');
        this._icon = new St.Icon({ gicon: new Gio.FileIcon({ file: iconFile }),
                                   style_class: 'panel-logo-icon' });
        box.add_actor(this._icon);

        /* Translators: If there is no suitable word for "Activities"
           in your language, you can use the word for "Overview". */
        this._label = new St.Label({ text: _("Activities"),
                                     y_align: Clutter.ActorAlign.CENTER });
        box.add_actor(this._label);

        this.actor.label_actor = this._label;

        this.actor.connect('captured-event', this._onCapturedEvent.bind(this));
        this.actor.connect_after('key-release-event', this._onKeyRelease.bind(this));

        Main.overview.connect('showing', () => {
            this.actor.add_style_pseudo_class('overview');
            this.actor.add_accessible_state (Atk.StateType.CHECKED);
        });
        Main.overview.connect('hiding', () => {
            this.actor.remove_style_pseudo_class('overview');
            this.actor.remove_accessible_state (Atk.StateType.CHECKED);
        });

        this._xdndTimeOut = 0;
    }

    handleDragOver(source, actor, x, y, time) {
        if (source != Main.xdndHandler)
            return DND.DragMotionResult.CONTINUE;

        if (this._xdndTimeOut != 0)
            Mainloop.source_remove(this._xdndTimeOut);
        this._xdndTimeOut = Mainloop.timeout_add(BUTTON_DND_ACTIVATION_TIMEOUT, () => {
            this._xdndToggleOverview(actor);
        });
        GLib.Source.set_name_by_id(this._xdndTimeOut, '[gnome-shell] this._xdndToggleOverview');

        return DND.DragMotionResult.CONTINUE;
    }

    _onCapturedEvent(actor, event) {
        if (event.type() == Clutter.EventType.BUTTON_PRESS ||
            event.type() == Clutter.EventType.TOUCH_BEGIN) {
            if (!Main.overview.shouldToggleByCornerOrButton())
                return Clutter.EVENT_STOP;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onEvent(actor, event) {
        super._onEvent(actor, event);

        if (event.type() == Clutter.EventType.TOUCH_END ||
            event.type() == Clutter.EventType.BUTTON_RELEASE)
            if (Main.overview.shouldToggleByCornerOrButton())
                Main.overview.toggle();

        return Clutter.EVENT_PROPAGATE;
    }

    _onKeyRelease(actor, event) {
        let symbol = event.get_key_symbol();
        if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) {
            if (Main.overview.shouldToggleByCornerOrButton())
                Main.overview.toggle();
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _xdndToggleOverview(actor) {
        let [x, y, mask] = global.get_pointer();
        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);

        if (pickedActor == this.actor && Main.overview.shouldToggleByCornerOrButton())
            Main.overview.toggle();

        Mainloop.source_remove(this._xdndTimeOut);
        this._xdndTimeOut = 0;
        return GLib.SOURCE_REMOVE;
    }
});

var PanelCorner = class {
    constructor(side) {
        this._side = side;

        this.actor = new St.DrawingArea({ style_class: 'panel-corner' });
        this.actor.connect('style-changed', this._styleChanged.bind(this));
        this.actor.connect('repaint', this._repaint.bind(this));
    }

    _findRightmostButton(container) {
        if (!container.get_children)
            return null;

        let children = container.get_children();

        if (!children || children.length == 0)
            return null;

        // Start at the back and work backward
        let index;
        for (index = children.length - 1; index >= 0; index--) {
            if (children[index].visible)
                break;
        }
        if (index < 0)
            return null;

        if (!(children[index].has_style_class_name('panel-menu')) &&
            !(children[index].has_style_class_name('panel-button')))
            return this._findRightmostButton(children[index]);

        return children[index];
    }

    _findLeftmostButton(container) {
        if (!container.get_children)
            return null;

        let children = container.get_children();

        if (!children || children.length == 0)
            return null;

        // Start at the front and work forward
        let index;
        for (index = 0; index < children.length; index++) {
            if (children[index].visible)
                break;
        }
        if (index == children.length)
            return null;

        if (!(children[index].has_style_class_name('panel-menu')) &&
            !(children[index].has_style_class_name('panel-button')))
            return this._findLeftmostButton(children[index]);

        return children[index];
    }

    setStyleParent(box) {
        let side = this._side;

        let rtlAwareContainer = box instanceof St.BoxLayout;
        if (rtlAwareContainer &&
            box.get_text_direction() == Clutter.TextDirection.RTL) {
            if (this._side == St.Side.LEFT)
                side = St.Side.RIGHT;
            else if (this._side == St.Side.RIGHT)
                side = St.Side.LEFT;
        }

        let button;
        if (side == St.Side.LEFT)
            button = this._findLeftmostButton(box);
        else if (side == St.Side.RIGHT)
            button = this._findRightmostButton(box);

        if (button) {
            if (this._button && this._buttonStyleChangedSignalId) {
                this._button.disconnect(this._buttonStyleChangedSignalId);
                this._button.style = null;
            }

            this._button = button;

            button.connect('destroy', () => {
                if (this._button == button) {
                    this._button = null;
                    this._buttonStyleChangedSignalId = 0;
                }
            });

            // Synchronize the locate button's pseudo classes with this corner
            this._buttonStyleChangedSignalId = button.connect('style-changed',
                actor => {
                    let pseudoClass = button.get_style_pseudo_class();
                    this.actor.set_style_pseudo_class(pseudoClass);
                });

            // The corner doesn't support theme transitions, so override
            // the .panel-button default
            button.style = 'transition-duration: 0ms';
        }
    }

    _repaint() {
        let node = this.actor.get_theme_node();

        let cornerRadius = node.get_length("-panel-corner-radius");
        let borderWidth = node.get_length('-panel-corner-border-width');

        let backgroundColor = node.get_color('-panel-corner-background-color');
        let borderColor = node.get_color('-panel-corner-border-color');

        let overlap = borderColor.alpha != 0;
        let offsetY = overlap ? 0 : borderWidth;

        let cr = this.actor.get_context();
        cr.setOperator(Cairo.Operator.SOURCE);

        cr.moveTo(0, offsetY);
        if (this._side == St.Side.LEFT)
            cr.arc(cornerRadius,
                   borderWidth + cornerRadius,
                   cornerRadius, Math.PI, 3 * Math.PI / 2);
        else
            cr.arc(0,
                   borderWidth + cornerRadius,
                   cornerRadius, 3 * Math.PI / 2, 2 * Math.PI);
        cr.lineTo(cornerRadius, offsetY);
        cr.closePath();

        let savedPath = cr.copyPath();

        let xOffsetDirection = this._side == St.Side.LEFT ? -1 : 1;
        let over = _over(borderColor, backgroundColor);
        Clutter.cairo_set_source_color(cr, over);
        cr.fill();

        if (overlap) {
            let offset = borderWidth;
            Clutter.cairo_set_source_color(cr, backgroundColor);

            cr.save();
            cr.translate(xOffsetDirection * offset, - offset);
            cr.appendPath(savedPath);
            cr.fill();
            cr.restore();
        }

        cr.$dispose();
    }

    _styleChanged() {
        let node = this.actor.get_theme_node();

        let cornerRadius = node.get_length("-panel-corner-radius");
        let borderWidth = node.get_length('-panel-corner-border-width');

        this.actor.set_size(cornerRadius, borderWidth + cornerRadius);
        this.actor.set_anchor_point(0, borderWidth);
    }
};

var AggregateLayout = GObject.registerClass(
class AggregateLayout extends Clutter.BoxLayout {
    _init(params) {
        if (!params)
            params = {};
        params['orientation'] = Clutter.Orientation.VERTICAL;
        super._init(params);

        this._sizeChildren = [];
    }

    addSizeChild(actor) {
        this._sizeChildren.push(actor);
        this.layout_changed();
    }

    vfunc_get_preferred_width(container, forHeight) {
        let themeNode = container.get_theme_node();
        let minWidth = themeNode.get_min_width();
        let natWidth = minWidth;

        for (let i = 0; i < this._sizeChildren.length; i++) {
            let child = this._sizeChildren[i];
            let [childMin, childNat] = child.get_preferred_width(forHeight);
            minWidth = Math.max(minWidth, childMin);
            natWidth = Math.max(natWidth, childNat);
        }
        return [minWidth, natWidth];
    }
});

var AggregateMenu = GObject.registerClass(
class AggregateMenu extends PanelMenu.Button {
    _init() {
        super._init(0.0, C_("System menu in the top bar", "System"), false);
        this.menu.actor.add_style_class_name('aggregate-menu');

        let menuLayout = new AggregateLayout();
        this.menu.box.set_layout_manager(menuLayout);

        this._indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box' });
        this.actor.add_child(this._indicators);

        if (Config.HAVE_NETWORKMANAGER) {
            this._network = new imports.ui.status.network.NMApplet();
        } else {
            this._network = null;
        }
        if (Config.HAVE_BLUETOOTH) {
            this._bluetooth = new imports.ui.status.bluetooth.Indicator();
        } else {
            this._bluetooth = null;
        }

        this._remoteAccess = new imports.ui.status.remoteAccess.RemoteAccessApplet();
        this._power = new imports.ui.status.power.Indicator();
        this._rfkill = new imports.ui.status.rfkill.Indicator();
        this._volume = new imports.ui.status.volume.Indicator();
        this._brightness = new imports.ui.status.brightness.Indicator();
        this._system = new imports.ui.status.system.Indicator();
        this._screencast = new imports.ui.status.screencast.Indicator();
        this._location = new imports.ui.status.location.Indicator();
        this._nightLight = new imports.ui.status.nightLight.Indicator();
        this._thunderbolt = new imports.ui.status.thunderbolt.Indicator();

        this._indicators.add_child(this._thunderbolt.indicators);
        this._indicators.add_child(this._screencast.indicators);
        this._indicators.add_child(this._location.indicators);
        this._indicators.add_child(this._nightLight.indicators);
        if (this._network) {
            this._indicators.add_child(this._network.indicators);
        }
        if (this._bluetooth) {
            this._indicators.add_child(this._bluetooth.indicators);
        }
        this._indicators.add_child(this._remoteAccess.indicators);
        this._indicators.add_child(this._rfkill.indicators);
        this._indicators.add_child(this._volume.indicators);
        this._indicators.add_child(this._power.indicators);
        this._indicators.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));

        this.menu.addMenuItem(this._volume.menu);
        this.menu.addMenuItem(this._brightness.menu);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        if (this._network) {
            this.menu.addMenuItem(this._network.menu);
        }
        if (this._bluetooth) {
            this.menu.addMenuItem(this._bluetooth.menu);
        }
        this.menu.addMenuItem(this._remoteAccess.menu);
        this.menu.addMenuItem(this._location.menu);
        this.menu.addMenuItem(this._rfkill.menu);
        this.menu.addMenuItem(this._power.menu);
        this.menu.addMenuItem(this._nightLight.menu);
        this.menu.addMenuItem(this._system.menu);

        menuLayout.addSizeChild(this._location.menu.actor);
        menuLayout.addSizeChild(this._rfkill.menu.actor);
        menuLayout.addSizeChild(this._power.menu.actor);
        menuLayout.addSizeChild(this._system.buttonGroup);
    }
});

const PANEL_ITEM_IMPLEMENTATIONS = {
    'activities': ActivitiesButton,
    'aggregateMenu': AggregateMenu,
    'appMenu': AppMenuButton,
    'dateMenu': imports.ui.dateMenu.DateMenuButton,
    'a11y': imports.ui.status.accessibility.ATIndicator,
    'keyboard': imports.ui.status.keyboard.InputSourceIndicator,
};

var Panel = GObject.registerClass(
class Panel extends St.Widget {
    _init() {
        super._init({ name: 'panel',
                      reactive: true });

        // For compatibility with extensions that still use the
        // this.actor field
        this.actor = this;
        this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);

        this._sessionStyle = null;

        this.statusArea = {};

        this.menuManager = new PopupMenu.PopupMenuManager(this);

        this._leftBox = new St.BoxLayout({ name: 'panelLeft' });
        this.add_child(this._leftBox);
        this._centerBox = new St.BoxLayout({ name: 'panelCenter' });
        this.add_child(this._centerBox);
        this._rightBox = new St.BoxLayout({ name: 'panelRight' });
        this.add_child(this._rightBox);

        this._leftCorner = new PanelCorner(St.Side.LEFT);
        this.add_child(this._leftCorner.actor);

        this._rightCorner = new PanelCorner(St.Side.RIGHT);
        this.add_child(this._rightCorner.actor);

        this.connect('button-press-event', this._onButtonPress.bind(this));
        this.connect('touch-event', this._onButtonPress.bind(this));
        this.connect('key-press-event', this._onKeyPress.bind(this));

        Main.overview.connect('showing', () => {
            this.add_style_pseudo_class('overview');
        });
        Main.overview.connect('hiding', () => {
            this.remove_style_pseudo_class('overview');
        });

        Main.layoutManager.panelBox.add(this);
        Main.ctrlAltTabManager.addGroup(this, _("Top Bar"), 'focus-top-bar-symbolic',
                                        { sortGroup: CtrlAltTab.SortGroup.TOP });

        Main.sessionMode.connect('updated', this._updatePanel.bind(this));

        global.display.connect('workareas-changed', () => { this.queue_relayout(); });
        this._updatePanel();
    }

    vfunc_get_preferred_width(forHeight) {
        let primaryMonitor = Main.layoutManager.primaryMonitor;

        if (primaryMonitor)
            return [0, primaryMonitor.width];

        return [0,  0];
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let allocWidth = box.x2 - box.x1;
        let allocHeight = box.y2 - box.y1;

        let [leftMinWidth, leftNaturalWidth] = this._leftBox.get_preferred_width(-1);
        let [centerMinWidth, centerNaturalWidth] = this._centerBox.get_preferred_width(-1);
        let [rightMinWidth, rightNaturalWidth] = this._rightBox.get_preferred_width(-1);

        let sideWidth, centerWidth;
        centerWidth = centerNaturalWidth;

        // get workspace area and center date entry relative to it
        let monitor = Main.layoutManager.findMonitorForActor(this);
        let centerOffset = 0;
        if (monitor) {
            let workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index);
            centerOffset = 2 * (workArea.x - monitor.x) + workArea.width - monitor.width;
        }

        sideWidth = Math.max(0, (allocWidth - centerWidth + centerOffset) / 2);

        let childBox = new Clutter.ActorBox();

        childBox.y1 = 0;
        childBox.y2 = allocHeight;
        if (this.get_text_direction() == Clutter.TextDirection.RTL) {
            childBox.x1 = Math.max(allocWidth - Math.min(Math.floor(sideWidth),
                                                         leftNaturalWidth),
                                   0);
            childBox.x2 = allocWidth;
        } else {
            childBox.x1 = 0;
            childBox.x2 = Math.min(Math.floor(sideWidth),
                                   leftNaturalWidth);
        }
        this._leftBox.allocate(childBox, flags);

        childBox.x1 = Math.ceil(sideWidth);
        childBox.y1 = 0;
        childBox.x2 = childBox.x1 + centerWidth;
        childBox.y2 = allocHeight;
        this._centerBox.allocate(childBox, flags);

        childBox.y1 = 0;
        childBox.y2 = allocHeight;
        if (this.get_text_direction() == Clutter.TextDirection.RTL) {
            childBox.x1 = 0;
            childBox.x2 = Math.min(Math.floor(sideWidth),
                                   rightNaturalWidth);
        } else {
            childBox.x1 = Math.max(allocWidth - Math.min(Math.floor(sideWidth),
                                                         rightNaturalWidth),
                                   0);
            childBox.x2 = allocWidth;
        }
        this._rightBox.allocate(childBox, flags);

        let cornerMinWidth, cornerMinHeight;
        let cornerWidth, cornerHeight;

        [cornerMinWidth, cornerWidth] = this._leftCorner.actor.get_preferred_width(-1);
        [cornerMinHeight, cornerHeight] = this._leftCorner.actor.get_preferred_height(-1);
        childBox.x1 = 0;
        childBox.x2 = cornerWidth;
        childBox.y1 = allocHeight;
        childBox.y2 = allocHeight + cornerHeight;
        this._leftCorner.actor.allocate(childBox, flags);

        [cornerMinWidth, cornerWidth] = this._rightCorner.actor.get_preferred_width(-1);
        [cornerMinHeight, cornerHeight] = this._rightCorner.actor.get_preferred_height(-1);
        childBox.x1 = allocWidth - cornerWidth;
        childBox.x2 = allocWidth;
        childBox.y1 = allocHeight;
        childBox.y2 = allocHeight + cornerHeight;
        this._rightCorner.actor.allocate(childBox, flags);
    }

    _onButtonPress(actor, event) {
        if (Main.modalCount > 0)
            return Clutter.EVENT_PROPAGATE;

        if (event.get_source() != actor)
            return Clutter.EVENT_PROPAGATE;

        let type = event.type();
        let isPress = type == Clutter.EventType.BUTTON_PRESS;
        if (!isPress && type != Clutter.EventType.TOUCH_BEGIN)
            return Clutter.EVENT_PROPAGATE;

        let button = isPress ? event.get_button() : -1;
        if (isPress && button != 1)
            return Clutter.EVENT_PROPAGATE;

        let focusWindow = global.display.focus_window;
        if (!focusWindow)
            return Clutter.EVENT_PROPAGATE;

        let dragWindow = focusWindow.is_attached_dialog() ? focusWindow.get_transient_for()
                                                          : focusWindow;
        if (!dragWindow)
            return Clutter.EVENT_PROPAGATE;

        let rect = dragWindow.get_frame_rect();
        let [stageX, stageY] = event.get_coords();

        let allowDrag = dragWindow.maximized_vertically &&
                        stageX > rect.x && stageX < rect.x + rect.width;

        if (!allowDrag)
            return Clutter.EVENT_PROPAGATE;

        global.display.begin_grab_op(dragWindow,
                                     Meta.GrabOp.MOVING,
                                     false, /* pointer grab */
                                     true, /* frame action */
                                     button,
                                     event.get_state(),
                                     event.get_time(),
                                     stageX, stageY);

        return Clutter.EVENT_STOP;
    }

    _onKeyPress(actor, event) {
        let symbol = event.get_key_symbol();
        if (symbol == Clutter.KEY_Escape) {
            global.display.focus_default_window(event.get_time());
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _toggleMenu(indicator) {
        if (!indicator || !indicator.container.visible)
            return; // menu not supported by current session mode

        let menu = indicator.menu;
        if (!indicator.actor.reactive)
            return;

        menu.toggle();
        if (menu.isOpen)
            menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
    }

    toggleAppMenu() {
        this._toggleMenu(this.statusArea.appMenu);
    }

    toggleCalendar() {
        this._toggleMenu(this.statusArea.dateMenu);
    }

    closeCalendar() {
        let indicator = this.statusArea.dateMenu;
        if (!indicator) // calendar not supported by current session mode
            return;

        let menu = indicator.menu;
        if (!indicator.actor.reactive)
            return;

        menu.close();
    }

    set boxOpacity(value) {
        let isReactive = value > 0;

        this._leftBox.opacity = value;
        this._leftBox.reactive = isReactive;
        this._centerBox.opacity = value;
        this._centerBox.reactive = isReactive;
        this._rightBox.opacity = value;
        this._rightBox.reactive = isReactive;
    }

    get boxOpacity() {
        return this._leftBox.opacity;
    }

    _updatePanel() {
        let panel = Main.sessionMode.panel;
        this._hideIndicators();
        this._updateBox(panel.left, this._leftBox);
        this._updateBox(panel.center, this._centerBox);
        this._updateBox(panel.right, this._rightBox);

        if (panel.left.indexOf('dateMenu') != -1)
            Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
        else if (panel.right.indexOf('dateMenu') != -1)
            Main.messageTray.bannerAlignment = Clutter.ActorAlign.END;
        // Default to center if there is no dateMenu
        else
            Main.messageTray.bannerAlignment = Clutter.ActorAlign.CENTER;

        if (this._sessionStyle)
            this._removeStyleClassName(this._sessionStyle);

        this._sessionStyle = Main.sessionMode.panelStyle;
        if (this._sessionStyle)
            this._addStyleClassName(this._sessionStyle);

        if (this.get_text_direction() == Clutter.TextDirection.RTL) {
            this._leftCorner.setStyleParent(this._rightBox);
            this._rightCorner.setStyleParent(this._leftBox);
        } else {
            this._leftCorner.setStyleParent(this._leftBox);
            this._rightCorner.setStyleParent(this._rightBox);
        }
    }

    _hideIndicators() {
        for (let role in PANEL_ITEM_IMPLEMENTATIONS) {
            let indicator = this.statusArea[role];
            if (!indicator)
                continue;
            indicator.container.hide();
        }
    }

    _ensureIndicator(role) {
        let indicator = this.statusArea[role];
        if (!indicator) {
            let constructor = PANEL_ITEM_IMPLEMENTATIONS[role];
            if (!constructor) {
                // This icon is not implemented (this is a bug)
                return null;
            }
            indicator = new constructor(this);
            this.statusArea[role] = indicator;
        }
        return indicator;
    }

    _updateBox(elements, box) {
        let nChildren = box.get_n_children();

        for (let i = 0; i < elements.length; i++) {
            let role = elements[i];
            let indicator = this._ensureIndicator(role);
            if (indicator == null)
                continue;

            this._addToPanelBox(role, indicator, i + nChildren, box);
        }
    }

    _addToPanelBox(role, indicator, position, box) {
        let container = indicator.container;
        container.show();

        let parent = container.get_parent();
        if (parent)
            parent.remove_actor(container);


        box.insert_child_at_index(container, position);
        if (indicator.menu)
            this.menuManager.addMenu(indicator.menu);
        this.statusArea[role] = indicator;
        let destroyId = indicator.connect('destroy', emitter => {
            delete this.statusArea[role];
            emitter.disconnect(destroyId);
        });
        indicator.connect('menu-set', this._onMenuSet.bind(this));
        this._onMenuSet(indicator);
    }

    addToStatusArea(role, indicator, position, box) {
        if (this.statusArea[role])
            throw new Error('Extension point conflict: there is already a status indicator for role ' + role);

        if (!(indicator instanceof PanelMenu.Button))
            throw new TypeError('Status indicator must be an instance of PanelMenu.Button');

        position = position || 0;
        let boxes = {
            left: this._leftBox,
            center: this._centerBox,
            right: this._rightBox
        };
        let boxContainer = boxes[box] || this._rightBox;
        this.statusArea[role] = indicator;
        this._addToPanelBox(role, indicator, position, boxContainer);
        return indicator;
    }

    _addStyleClassName(className) {
        this.add_style_class_name(className);
        this._rightCorner.actor.add_style_class_name(className);
        this._leftCorner.actor.add_style_class_name(className);
    }

    _removeStyleClassName(className) {
        this.remove_style_class_name(className);
        this._rightCorner.actor.remove_style_class_name(className);
        this._leftCorner.actor.remove_style_class_name(className);
    }

    _onMenuSet(indicator) {
        if (!indicator.menu || indicator.menu.hasOwnProperty('_openChangedId'))
            return;

        indicator.menu._openChangedId = indicator.menu.connect('open-state-changed',
            (menu, isOpen) => {
                let boxAlignment;
                if (this._leftBox.contains(indicator.container))
                    boxAlignment = Clutter.ActorAlign.START;
                else if (this._centerBox.contains(indicator.container))
                    boxAlignment = Clutter.ActorAlign.CENTER;
                else if (this._rightBox.contains(indicator.container))
                    boxAlignment = Clutter.ActorAlign.END;

                if (boxAlignment == Main.messageTray.bannerAlignment)
                    Main.messageTray.bannerBlocked = isOpen;
            });
    }
});
(uuay)runDialog.js�)// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const Util = imports.misc.util;
const History = imports.misc.history;

var MAX_FILE_DELETED_BEFORE_INVALID = 10;

const HISTORY_KEY = 'command-history';

const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const DISABLE_COMMAND_LINE_KEY = 'disable-command-line';

const TERMINAL_SCHEMA = 'org.gnome.desktop.default-applications.terminal';
const EXEC_KEY = 'exec';
const EXEC_ARG_KEY = 'exec-arg';

var DIALOG_GROW_TIME = 0.1;

var RunDialog = class extends ModalDialog.ModalDialog {
    constructor() {
        super({ styleClass: 'run-dialog',
                destroyOnClose: false });

        this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
        this._terminalSettings = new Gio.Settings({ schema_id: TERMINAL_SCHEMA });
        global.settings.connect('changed::development-tools', () => {
            this._enableInternalCommands = global.settings.get_boolean('development-tools');
        });
        this._enableInternalCommands = global.settings.get_boolean('development-tools');

        this._internalCommands = { 'lg': () => {
                                       Main.createLookingGlass().open();
                                   },

                                   'r': this._restart.bind(this),

                                   // Developer brain backwards compatibility
                                   'restart': this._restart.bind(this),

                                   'debugexit': () => {
                                       Meta.quit(Meta.ExitCode.ERROR);
                                   },

                                   // rt is short for "reload theme"
                                   'rt': () => {
                                       Main.reloadThemeResource();
                                       Main.loadTheme();
                                   },

                                   'check_cloexec_fds': () => {
                                       Shell.util_check_cloexec_fds();
                                   },
                                 };


        let label = new St.Label({ style_class: 'run-dialog-label',
                                   text: _("Enter a Command") });

        this.contentLayout.add(label, { x_fill: false,
                                        x_align: St.Align.START,
                                        y_align: St.Align.START });

        let entry = new St.Entry({ style_class: 'run-dialog-entry',
                                   can_focus: true });
        ShellEntry.addContextMenu(entry);

        entry.label_actor = label;

        this._entryText = entry.clutter_text;
        this.contentLayout.add(entry, { y_align: St.Align.START });
        this.setInitialKeyFocus(this._entryText);

        this._errorBox = new St.BoxLayout({ style_class: 'run-dialog-error-box' });

        this.contentLayout.add(this._errorBox, { expand: true });

        let errorIcon = new St.Icon({ icon_name: 'dialog-error-symbolic',
                                      icon_size: 24,
                                      style_class: 'run-dialog-error-icon' });

        this._errorBox.add(errorIcon, { y_align: St.Align.MIDDLE });

        this._commandError = false;

        this._errorMessage = new St.Label({ style_class: 'run-dialog-error-label' });
        this._errorMessage.clutter_text.line_wrap = true;

        this._errorBox.add(this._errorMessage, { expand: true,
                                                 x_align: St.Align.START,
                                                 x_fill: false,
                                                 y_align: St.Align.MIDDLE,
                                                 y_fill: false });

        this._errorBox.hide();

        this.setButtons([{ action: this.close.bind(this),
                           label: _("Close"),
                           key: Clutter.Escape }]);

        this._pathCompleter = new Gio.FilenameCompleter();

        this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY,
                                                     entry: this._entryText });
        this._entryText.connect('activate', (o) => {
            this.popModal();
            this._run(o.get_text(),
                      Clutter.get_current_event().get_state() & Clutter.ModifierType.CONTROL_MASK);
            if (!this._commandError ||
                !this.pushModal())
                this.close();
        });
        this._entryText.connect('key-press-event', (o, e) => {
            let symbol = e.get_key_symbol();
            if (symbol == Clutter.Tab) {
                let text = o.get_text();
                let prefix;
                if (text.lastIndexOf(' ') == -1)
                    prefix = text;
                else
                    prefix = text.substr(text.lastIndexOf(' ') + 1);
                let postfix = this._getCompletion(prefix);
                if (postfix != null && postfix.length > 0) {
                    o.insert_text(postfix, -1);
                    o.set_cursor_position(text.length + postfix.length);
                }
                return Clutter.EVENT_STOP;
            }
            return Clutter.EVENT_PROPAGATE;
        });
    }

    _getCommandCompletion(text) {
        function _getCommon(s1, s2) {
            if (s1 == null)
                return s2;

            let k = 0;
            for (; k < s1.length && k < s2.length; k++) {
                if (s1[k] != s2[k])
                    break;
            }
            if (k == 0)
                return '';
            return s1.substr(0, k);
        }

        let paths = GLib.getenv('PATH').split(':');
        paths.push(GLib.get_home_dir());
        let someResults = paths.map(path => {
            let results = [];
            try {
                let file = Gio.File.new_for_path(path);
                let fileEnum = file.enumerate_children('standard::name', Gio.FileQueryInfoFlags.NONE, null);
                let info;
                while ((info = fileEnum.next_file(null))) {
                    let name = info.get_name();
                    if (name.slice(0, text.length) == text)
                        results.push(name);
                }
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND) &&
                    !e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_DIRECTORY))
                    log(e);
            } finally {
                return results;
            }
        });
        let results = someResults.reduce((a, b) => a.concat(b), []);

        if (!results.length)
            return null;

        let common = results.reduce(_getCommon, null);
        return common.substr(text.length);
    }

    _getCompletion(text) {
        if (text.indexOf('/') != -1) {
            return this._pathCompleter.get_completion_suffix(text);
        } else {
            return this._getCommandCompletion(text);
        }
    }

    _run(input, inTerminal) {
        let command = input;

        this._history.addItem(input);
        this._commandError = false;
        let f;
        if (this._enableInternalCommands)
            f = this._internalCommands[input];
        else
            f = null;
        if (f) {
            f();
        } else if (input) {
            try {
                if (inTerminal) {
                    let exec = this._terminalSettings.get_string(EXEC_KEY);
                    let exec_arg = this._terminalSettings.get_string(EXEC_ARG_KEY);
                    command = exec + ' ' + exec_arg + ' ' + input;
                }
                Util.trySpawnCommandLine(command);
            } catch (e) {
                // Mmmh, that failed - see if @input matches an existing file
                let path = null;
                if (input.charAt(0) == '/') {
                    path = input;
                } else {
                    if (input.charAt(0) == '~')
                        input = input.slice(1);
                    path = GLib.get_home_dir() + '/' + input;
                }

                if (GLib.file_test(path, GLib.FileTest.EXISTS)) {
                    let file = Gio.file_new_for_path(path);
                    try {
                        Gio.app_info_launch_default_for_uri(file.get_uri(),
                                                            global.create_app_launch_context(0, -1));
                    } catch (e) {
                        // The exception from gjs contains an error string like:
                        //     Error invoking Gio.app_info_launch_default_for_uri: No application
                        //     is registered as handling this file
                        // We are only interested in the part after the first colon.
                        let message = e.message.replace(/[^:]*: *(.+)/, '$1');
                        this._showError(message);
                    }
                } else {
                    this._showError(e.message);
                }
            }
        }
    }

    _showError(message) {
        this._commandError = true;

        this._errorMessage.set_text(message);

        if (!this._errorBox.visible) {
            let [errorBoxMinHeight, errorBoxNaturalHeight] = this._errorBox.get_preferred_height(-1);

            let parentActor = this._errorBox.get_parent();
            Tweener.addTween(parentActor,
                             { height: parentActor.height + errorBoxNaturalHeight,
                               time: DIALOG_GROW_TIME,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   parentActor.set_height(-1);
                                   this._errorBox.show();
                               }
                             });
        }
    }

    _restart() {
        if (Meta.is_wayland_compositor()) {
            this._showError(_("Restart is not available on Wayland"));
            return;
        }
        this._shouldFadeOut = false;
        this.close();
        Meta.restart(_("Restarting…"));
    }

    open() {
        this._history.lastItem();
        this._errorBox.hide();
        this._entryText.set_text('');
        this._commandError = false;

        if (this._lockdownSettings.get_boolean(DISABLE_COMMAND_LINE_KEY))
            return;

        super.open();
    }
};
Signals.addSignalMethods(RunDialog.prototype);
(uuay)calendar.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Shell, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const MessageList = imports.ui.messageList;
const MessageTray = imports.ui.messageTray;
const Mpris = imports.ui.mpris;
const Util = imports.misc.util;

const { loadInterfaceXML } = imports.misc.fileUtils;

var MSECS_IN_DAY = 24 * 60 * 60 * 1000;
var SHOW_WEEKDATE_KEY = 'show-weekdate';
var ELLIPSIS_CHAR = '\u2026';

var MESSAGE_ICON_SIZE = -1; // pick up from CSS

var NC_ = (context, str) => context + '\u0004' + str;

function sameYear(dateA, dateB) {
    return (dateA.getYear() == dateB.getYear());
}

function sameMonth(dateA, dateB) {
    return sameYear(dateA, dateB) && (dateA.getMonth() == dateB.getMonth());
}

function sameDay(dateA, dateB) {
    return sameMonth(dateA, dateB) && (dateA.getDate() == dateB.getDate());
}

function isToday(date) {
    return sameDay(new Date(), date);
}

function _isWorkDay(date) {
    /* Translators: Enter 0-6 (Sunday-Saturday) for non-work days. Examples: "0" (Sunday) "6" (Saturday) "06" (Sunday and Saturday). */
    let days = C_('calendar-no-work', "06");
    return days.indexOf(date.getDay().toString()) == -1;
}

function _getBeginningOfDay(date) {
    let ret = new Date(date.getTime());
    ret.setHours(0);
    ret.setMinutes(0);
    ret.setSeconds(0);
    ret.setMilliseconds(0);
    return ret;
}

function _getEndOfDay(date) {
    let ret = new Date(date.getTime());
    ret.setHours(23);
    ret.setMinutes(59);
    ret.setSeconds(59);
    ret.setMilliseconds(999);
    return ret;
}

function _getCalendarDayAbbreviation(dayNumber) {
    let abbreviations = [
        /* Translators: Calendar grid abbreviation for Sunday.
         *
         * NOTE: These grid abbreviations are always shown together
         * and in order, e.g. "S M T W T F S".
         */
        NC_("grid sunday", "S"),
        /* Translators: Calendar grid abbreviation for Monday */
        NC_("grid monday", "M"),
        /* Translators: Calendar grid abbreviation for Tuesday */
        NC_("grid tuesday", "T"),
        /* Translators: Calendar grid abbreviation for Wednesday */
        NC_("grid wednesday", "W"),
        /* Translators: Calendar grid abbreviation for Thursday */
        NC_("grid thursday", "T"),
        /* Translators: Calendar grid abbreviation for Friday */
        NC_("grid friday", "F"),
        /* Translators: Calendar grid abbreviation for Saturday */
        NC_("grid saturday", "S")
    ];
    return Shell.util_translate_time_string(abbreviations[dayNumber]);
}

// Abstraction for an appointment/event in a calendar

var CalendarEvent = class CalendarEvent {
    constructor(id, date, end, summary, allDay) {
        this.id = id;
        this.date = date;
        this.end = end;
        this.summary = summary;
        this.allDay = allDay;
    }
};

// Interface for appointments/events - e.g. the contents of a calendar
//

// First, an implementation with no events
var EmptyEventSource = class EmptyEventSource {
    constructor() {
        this.isLoading = false;
        this.isDummy = true;
        this.hasCalendars = false;
    }

    destroy() {
    }

    requestRange(begin, end) {
    }

    getEvents(begin, end) {
        let result = [];
        return result;
    }

    hasEvents(day) {
        return false;
    }
};
Signals.addSignalMethods(EmptyEventSource.prototype);

const CalendarServerIface = loadInterfaceXML('org.gnome.Shell.CalendarServer');

const CalendarServerInfo  = Gio.DBusInterfaceInfo.new_for_xml(CalendarServerIface);

function CalendarServer() {
    return new Gio.DBusProxy({ g_connection: Gio.DBus.session,
                               g_interface_name: CalendarServerInfo.name,
                               g_interface_info: CalendarServerInfo,
                               g_name: 'org.gnome.Shell.CalendarServer',
                               g_object_path: '/org/gnome/Shell/CalendarServer' });
}

function _datesEqual(a, b) {
    if (a < b)
        return false;
    else if (a > b)
        return false;
    return true;
}

function _dateIntervalsOverlap(a0, a1, b0, b1)
{
    if (a1 <= b0)
        return false;
    else if (b1 <= a0)
        return false;
    else
        return true;
}

// an implementation that reads data from a session bus service
var DBusEventSource = class DBusEventSource {
    constructor() {
        this._resetCache();
        this.isLoading = false;
        this.isDummy = false;

        this._initialized = false;
        this._dbusProxy = new CalendarServer();
        this._dbusProxy.init_async(GLib.PRIORITY_DEFAULT, null, (object, result) => {
            let loaded = false;

            try {
                this._dbusProxy.init_finish(result);
                loaded = true;
            } catch(e) {
                if (e.matches(Gio.DBusError, Gio.DBusError.TIMED_OUT)) {
                    // Ignore timeouts and install signals as normal, because with high
                    // probability the service will appear later on, and we will get a
                    // NameOwnerChanged which will finish loading
                    //
                    // (But still _initialized to false, because the proxy does not know
                    // about the HasCalendars property and would cause an exception trying
                    // to read it)
                } else {
                    log('Error loading calendars: ' + e.message);
                    return;
                }
            }

            this._dbusProxy.connectSignal('Changed', this._onChanged.bind(this));

            this._dbusProxy.connect('notify::g-name-owner', () => {
                if (this._dbusProxy.g_name_owner)
                    this._onNameAppeared();
                else
                    this._onNameVanished();
            });

            this._dbusProxy.connect('g-properties-changed', () => {
                this.emit('notify::has-calendars');
            });

            this._initialized = loaded;
            if (loaded) {
                this.emit('notify::has-calendars');
                this._onNameAppeared();
            }
        });
    }

    destroy() {
        this._dbusProxy.run_dispose();
    }

    get hasCalendars() {
        if (this._initialized)
            return this._dbusProxy.HasCalendars;
        else
            return false;
    }

    _resetCache() {
        this._events = [];
        this._lastRequestBegin = null;
        this._lastRequestEnd = null;
    }

    _onNameAppeared(owner) {
        this._initialized = true;
        this._resetCache();
        this._loadEvents(true);
    }

    _onNameVanished(oldOwner) {
        this._resetCache();
        this.emit('changed');
    }

    _onChanged() {
        this._loadEvents(false);
    }

    _onEventsReceived(results, error) {
        let newEvents = [];
        let appointments = results ? results[0] : null;
        if (appointments != null) {
            for (let n = 0; n < appointments.length; n++) {
                let a = appointments[n];
                let date = new Date(a[4] * 1000);
                let end = new Date(a[5] * 1000);
                let id = a[0];
                let summary = a[1];
                let allDay = a[3];
                let event = new CalendarEvent(id, date, end, summary, allDay);
                newEvents.push(event);
            }
            newEvents.sort((ev1, ev2) => ev1.date.getTime() - ev2.date.getTime());
        }

        this._events = newEvents;
        this.isLoading = false;
        this.emit('changed');
    }

    _loadEvents(forceReload) {
        // Ignore while loading
        if (!this._initialized)
            return;

        if (this._curRequestBegin && this._curRequestEnd){
            this._dbusProxy.GetEventsRemote(this._curRequestBegin.getTime() / 1000,
                                            this._curRequestEnd.getTime() / 1000,
                                            forceReload,
                                            this._onEventsReceived.bind(this),
                                            Gio.DBusCallFlags.NONE);
        }
    }

    requestRange(begin, end) {
        if (!(_datesEqual(begin, this._lastRequestBegin) && _datesEqual(end, this._lastRequestEnd))) {
            this.isLoading = true;
            this._lastRequestBegin = begin;
            this._lastRequestEnd = end;
            this._curRequestBegin = begin;
            this._curRequestEnd = end;
            this._loadEvents(false);
        }
    }

    getEvents(begin, end) {
        let result = [];
        for(let n = 0; n < this._events.length; n++) {
            let event = this._events[n];

            if (_dateIntervalsOverlap (event.date, event.end, begin, end)) {
                result.push(event);
            }
        }
        result.sort((event1, event2) => {
            // sort events by end time on ending day
            let d1 = event1.date < begin && event1.end <= end ? event1.end : event1.date;
            let d2 = event2.date < begin && event2.end <= end ? event2.end : event2.date;
            return d1.getTime() - d2.getTime();
        });
        return result;
    }

    hasEvents(day) {
        let dayBegin = _getBeginningOfDay(day);
        let dayEnd = _getEndOfDay(day);

        let events = this.getEvents(dayBegin, dayEnd);

        if (events.length == 0)
            return false;

        return true;
    }
};
Signals.addSignalMethods(DBusEventSource.prototype);

var Calendar = class Calendar {
    constructor() {
        this._weekStart = Shell.util_get_week_start();
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.calendar' });

        this._settings.connect('changed::' + SHOW_WEEKDATE_KEY, this._onSettingsChange.bind(this));
        this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);

        /**
         * Translators: The header displaying just the month name
         * standalone, when this is a month of the current year.
         * "%OB" is the new format specifier introduced in glibc 2.27,
         * in most cases you should not change it.
         */
        this._headerFormatWithoutYear = _('%OB');
        /**
         * Translators: The header displaying the month name and the year
         * number, when this is a month of a different year.  You can
         * reorder the format specifiers or add other modifications
         * according to the requirements of your language.
         * "%OB" is the new format specifier introduced in glibc 2.27,
         * in most cases you should not use the old "%B" here unless you
         * absolutely know what you are doing.
         */
        this._headerFormat = _('%OB %Y');

        // Start off with the current date
        this._selectedDate = new Date();

        this._shouldDateGrabFocus = false;

        this.actor = new St.Widget({ style_class: 'calendar',
                                     layout_manager: new Clutter.TableLayout(),
                                     reactive: true });

        this.actor.connect('scroll-event',
                           this._onScroll.bind(this));

        this._buildHeader ();
    }

    // @eventSource: is an object implementing the EventSource API, e.g. the
    // requestRange(), getEvents(), hasEvents() methods and the ::changed signal.
    setEventSource(eventSource) {
        this._eventSource = eventSource;
        this._eventSource.connect('changed', () => {
            this._rebuildCalendar();
            this._update();
        });
        this._rebuildCalendar();
        this._update();
    }

    // Sets the calendar to show a specific date
    setDate(date) {
        if (sameDay(date, this._selectedDate))
            return;

        this._selectedDate = date;
        this._update();
        this.emit('selected-date-changed', new Date(this._selectedDate));
    }

    updateTimeZone() {
        // The calendar need to be rebuilt after a time zone update because
        // the date might have changed.
        this._rebuildCalendar();
        this._update();
    }

    _buildHeader() {
        let layout = this.actor.layout_manager;
        let offsetCols = this._useWeekdate ? 1 : 0;
        this.actor.destroy_all_children();

        // Top line of the calendar '<| September 2009 |>'
        this._topBox = new St.BoxLayout();
        layout.pack(this._topBox, 0, 0);
        layout.set_span(this._topBox, offsetCols + 7, 1);

        this._backButton = new St.Button({ style_class: 'calendar-change-month-back pager-button',
                                           accessible_name: _("Previous month"),
                                           can_focus: true });
        this._backButton.add_actor(new St.Icon({ icon_name: 'pan-start-symbolic' }));
        this._topBox.add(this._backButton);
        this._backButton.connect('clicked', this._onPrevMonthButtonClicked.bind(this));

        this._monthLabel = new St.Label({style_class: 'calendar-month-label',
                                         can_focus: true });
        this._topBox.add(this._monthLabel, { expand: true, x_fill: false, x_align: St.Align.MIDDLE });

        this._forwardButton = new St.Button({ style_class: 'calendar-change-month-forward pager-button',
                                              accessible_name: _("Next month"),
                                              can_focus: true });
        this._forwardButton.add_actor(new St.Icon({ icon_name: 'pan-end-symbolic' }));
        this._topBox.add(this._forwardButton);
        this._forwardButton.connect('clicked', this._onNextMonthButtonClicked.bind(this));

        // Add weekday labels...
        //
        // We need to figure out the abbreviated localized names for the days of the week;
        // we do this by just getting the next 7 days starting from right now and then putting
        // them in the right cell in the table. It doesn't matter if we add them in order
        let iter = new Date(this._selectedDate);
        iter.setSeconds(0); // Leap second protection. Hah!
        iter.setHours(12);
        for (let i = 0; i < 7; i++) {
            // Could use iter.toLocaleFormat('%a') but that normally gives three characters
            // and we want, ideally, a single character for e.g. S M T W T F S
            let customDayAbbrev = _getCalendarDayAbbreviation(iter.getDay());
            let label = new St.Label({ style_class: 'calendar-day-base calendar-day-heading',
                                       text: customDayAbbrev,
                                       can_focus: true });
            label.accessible_name = iter.toLocaleFormat('%A');
            let col;
            if (this.actor.get_text_direction() == Clutter.TextDirection.RTL)
                col = 6 - (7 + iter.getDay() - this._weekStart) % 7;
            else
                col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7;
            layout.pack(label, col, 1);
            iter.setTime(iter.getTime() + MSECS_IN_DAY);
        }

        // All the children after this are days, and get removed when we update the calendar
        this._firstDayIndex = this.actor.get_n_children();
    }

    _onScroll(actor, event) {
        switch (event.get_scroll_direction()) {
        case Clutter.ScrollDirection.UP:
        case Clutter.ScrollDirection.LEFT:
            this._onPrevMonthButtonClicked();
            break;
        case Clutter.ScrollDirection.DOWN:
        case Clutter.ScrollDirection.RIGHT:
            this._onNextMonthButtonClicked();
            break;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onPrevMonthButtonClicked() {
        let newDate = new Date(this._selectedDate);
        let oldMonth = newDate.getMonth();
        if (oldMonth == 0) {
            newDate.setMonth(11);
            newDate.setFullYear(newDate.getFullYear() - 1);
            if (newDate.getMonth() != 11) {
                let day = 32 - new Date(newDate.getFullYear() - 1, 11, 32).getDate();
                newDate = new Date(newDate.getFullYear() - 1, 11, day);
            }
        }
        else {
            newDate.setMonth(oldMonth - 1);
            if (newDate.getMonth() != oldMonth - 1) {
                let day = 32 - new Date(newDate.getFullYear(), oldMonth - 1, 32).getDate();
                newDate = new Date(newDate.getFullYear(), oldMonth - 1, day);
            }
        }

        this._backButton.grab_key_focus();

        this.setDate(newDate);
    }

    _onNextMonthButtonClicked() {
        let newDate = new Date(this._selectedDate);
        let oldMonth = newDate.getMonth();
        if (oldMonth == 11) {
            newDate.setMonth(0);
            newDate.setFullYear(newDate.getFullYear() + 1);
            if (newDate.getMonth() != 0) {
                let day = 32 - new Date(newDate.getFullYear() + 1, 0, 32).getDate();
                newDate = new Date(newDate.getFullYear() + 1, 0, day);
            }
        }
        else {
            newDate.setMonth(oldMonth + 1);
            if (newDate.getMonth() != oldMonth + 1) {
                let day = 32 - new Date(newDate.getFullYear(), oldMonth + 1, 32).getDate();
                newDate = new Date(newDate.getFullYear(), oldMonth + 1, day);
            }
        }

        this._forwardButton.grab_key_focus();

        this.setDate(newDate);
    }

    _onSettingsChange() {
        this._useWeekdate = this._settings.get_boolean(SHOW_WEEKDATE_KEY);
        this._buildHeader();
        this._rebuildCalendar();
        this._update();
    }

    _rebuildCalendar() {
        let now = new Date();

        // Remove everything but the topBox and the weekday labels
        let children = this.actor.get_children();
        for (let i = this._firstDayIndex; i < children.length; i++)
            children[i].destroy();

        this._buttons = [];

        // Start at the beginning of the week before the start of the month
        //
        // We want to show always 6 weeks (to keep the calendar menu at the same
        // height if there are no events), so we pad it according to the following
        // policy:
        //
        // 1 - If a month has 6 weeks, we place no padding (example: Dec 2012)
        // 2 - If a month has 5 weeks and it starts on week start, we pad one week
        //     before it (example: Apr 2012)
        // 3 - If a month has 5 weeks and it starts on any other day, we pad one week
        //     after it (example: Nov 2012)
        // 4 - If a month has 4 weeks, we pad one week before and one after it
        //     (example: Feb 2010)
        //
        // Actually computing the number of weeks is complex, but we know that the
        // problematic categories (2 and 4) always start on week start, and that
        // all months at the end have 6 weeks.
        let beginDate = new Date(this._selectedDate);
        beginDate.setDate(1);
        beginDate.setSeconds(0);
        beginDate.setHours(12);

        this._calendarBegin = new Date(beginDate);
        this._markedAsToday = now;

        let year = beginDate.getYear();

        let daysToWeekStart = (7 + beginDate.getDay() - this._weekStart) % 7;
        let startsOnWeekStart = daysToWeekStart == 0;
        let weekPadding = startsOnWeekStart ? 7 : 0;

        beginDate.setTime(beginDate.getTime() - (weekPadding + daysToWeekStart) * MSECS_IN_DAY);

        let layout = this.actor.layout_manager;
        let iter = new Date(beginDate);
        let row = 2;
        // nRows here means 6 weeks + one header + one navbar
        let nRows = 8;
        while (row < 8) {
            // xgettext:no-javascript-format
            let button = new St.Button({ label: iter.toLocaleFormat(C_("date day number format", "%d")),
                                         can_focus: true });
            let rtl = button.get_text_direction() == Clutter.TextDirection.RTL;

            if (this._eventSource.isDummy)
                button.reactive = false;

            button._date = new Date(iter);
            button.connect('clicked', () => {
                this._shouldDateGrabFocus = true;
                this.setDate(button._date);
                this._shouldDateGrabFocus = false;
            });

            let hasEvents = this._eventSource.hasEvents(iter);
            let styleClass = 'calendar-day-base calendar-day';

            if (_isWorkDay(iter))
                styleClass += ' calendar-work-day';
            else
                styleClass += ' calendar-nonwork-day';

            // Hack used in lieu of border-collapse - see gnome-shell.css
            if (row == 2)
                styleClass = 'calendar-day-top ' + styleClass;

            let leftMost = rtl ? iter.getDay() == (this._weekStart + 6) % 7
                               : iter.getDay() == this._weekStart;
            if (leftMost)
                styleClass = 'calendar-day-left ' + styleClass;

            if (sameDay(now, iter))
                styleClass += ' calendar-today';
            else if (iter.getMonth() != this._selectedDate.getMonth())
                styleClass += ' calendar-other-month-day';

            if (hasEvents)
                styleClass += ' calendar-day-with-events';

            button.style_class = styleClass;

            let offsetCols = this._useWeekdate ? 1 : 0;
            let col;
            if (rtl)
                col = 6 - (7 + iter.getDay() - this._weekStart) % 7;
            else
                col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7;
            layout.pack(button, col, row);

            this._buttons.push(button);

            if (this._useWeekdate && iter.getDay() == 4) {
                let label = new St.Label({ text: iter.toLocaleFormat('%V'),
                                           style_class: 'calendar-day-base calendar-week-number',
                                           can_focus: true });
                let weekFormat = Shell.util_translate_time_string(N_("Week %V"));
                label.accessible_name = iter.toLocaleFormat(weekFormat);
                layout.pack(label, rtl ? 7 : 0, row);
            }

            iter.setTime(iter.getTime() + MSECS_IN_DAY);

            if (iter.getDay() == this._weekStart)
                row++;
        }

        // Signal to the event source that we are interested in events
        // only from this date range
        this._eventSource.requestRange(beginDate, iter);
    }

    _update() {
        let now = new Date();

        if (sameYear(this._selectedDate, now))
            this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormatWithoutYear);
        else
            this._monthLabel.text = this._selectedDate.toLocaleFormat(this._headerFormat);

        if (!this._calendarBegin || !sameMonth(this._selectedDate, this._calendarBegin) || !sameDay(now, this._markedAsToday))
            this._rebuildCalendar();

        this._buttons.forEach(button => {
            if (sameDay(button._date, this._selectedDate)) {
                button.add_style_pseudo_class('selected');
                if (this._shouldDateGrabFocus)
                    button.grab_key_focus();
            }
            else
                button.remove_style_pseudo_class('selected');
        });
    }
};
Signals.addSignalMethods(Calendar.prototype);

var EventMessage = class EventMessage extends MessageList.Message {
    constructor(event, date) {
        super('', event.summary);

        this._event = event;
        this._date = date;

        this.setTitle(this._formatEventTime());

        this._icon = new St.Icon({ icon_name: 'x-office-calendar-symbolic' });
        this.setIcon(this._icon);

        this.actor.connect('style-changed', () => {
            let iconVisible = this.actor.get_parent().has_style_pseudo_class('first-child');
            this._icon.opacity = (iconVisible ? 255 : 0);
        });
    }

    _formatEventTime() {
        let periodBegin = _getBeginningOfDay(this._date);
        let periodEnd = _getEndOfDay(this._date);
        let allDay = (this._event.allDay || (this._event.date <= periodBegin &&
                                             this._event.end >= periodEnd));
        let title;
        if (allDay) {
            /* Translators: Shown in calendar event list for all day events
             * Keep it short, best if you can use less then 10 characters
             */
            title = C_("event list time", "All Day");
        } else {
            let date = this._event.date >= periodBegin ? this._event.date
                                                       : this._event.end;
            title = Util.formatTime(date, { timeOnly: true });
        }

        let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;
        if (this._event.date < periodBegin && !this._event.allDay) {
            if (rtl)
                title = title + ELLIPSIS_CHAR;
            else
                title = ELLIPSIS_CHAR + title;
        }
        if (this._event.end > periodEnd && !this._event.allDay) {
            if (rtl)
                title = ELLIPSIS_CHAR + title;
            else
                title = title + ELLIPSIS_CHAR;
        }
        return title;
    }
};

var NotificationMessage =
class NotificationMessage extends MessageList.Message {
    constructor(notification) {
        super(notification.title, notification.bannerBodyText);
        this.setUseBodyMarkup(notification.bannerBodyMarkup);

        this.notification = notification;

        this.setIcon(this._getIcon());

        this.connect('close', () => {
            this._closed = true;
            if (this.notification)
                this.notification.destroy(MessageTray.NotificationDestroyedReason.DISMISSED);
        });
        this._destroyId = notification.connect('destroy', () => {
            this._disconnectNotificationSignals();
            this.notification = null;
            if (!this._closed)
                this.close();
        });
        this._updatedId = notification.connect('updated',
                                               this._onUpdated.bind(this));
    }

    _getIcon() {
        if (this.notification.gicon)
            return new St.Icon({ gicon: this.notification.gicon,
                                 icon_size: MESSAGE_ICON_SIZE });
        else
            return this.notification.source.createIcon(MESSAGE_ICON_SIZE);
    }

    _onUpdated(n, clear) {
        this.setIcon(this._getIcon());
        this.setTitle(n.title);
        this.setBody(n.bannerBodyText);
        this.setUseBodyMarkup(n.bannerBodyMarkup);
    }

    _onClicked() {
        this.notification.activate();
    }

    _onDestroy() {
        super._onDestroy();
        this._disconnectNotificationSignals();
    }

    _disconnectNotificationSignals() {
        if (this._updatedId)
            this.notification.disconnect(this._updatedId);
        this._updatedId = 0;

        if (this._destroyId)
            this.notification.disconnect(this._destroyId);
        this._destroyId = 0;
    }

    canClose() {
        return true;
    }
};

var EventsSection = class EventsSection extends MessageList.MessageListSection {
    constructor() {
        super();

        this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
        this._desktopSettings.connect('changed', this._reloadEvents.bind(this));
        this._eventSource = new EmptyEventSource();

        this._messageById = new Map();

        this._title = new St.Button({ style_class: 'events-section-title',
                                      label: '',
                                      x_align: St.Align.START,
                                      can_focus: true });
        this.actor.insert_child_below(this._title, null);

        this._title.connect('clicked', this._onTitleClicked.bind(this));
        this._title.connect('key-focus-in', this._onKeyFocusIn.bind(this));

        this._appSys = Shell.AppSystem.get_default();
        this._appSys.connect('installed-changed',
            this._appInstalledChanged.bind(this));
        this._appInstalledChanged();
    }

    setEventSource(eventSource) {
        this._eventSource = eventSource;
        this._eventSource.connect('changed', this._reloadEvents.bind(this));
    }

    get allowed() {
        return Main.sessionMode.showCalendarEvents;
    }

    _updateTitle() {
        this._title.visible = !isToday(this._date);

        if (!this._title.visible)
            return;

        let dayFormat;
        let now = new Date();
        if (sameYear(this._date, now))
            /* Translators: Shown on calendar heading when selected day occurs on current year */
            dayFormat = Shell.util_translate_time_string(NC_("calendar heading",
                                                             "%A, %B %-d"));
        else
            /* Translators: Shown on calendar heading when selected day occurs on different year */
            dayFormat = Shell.util_translate_time_string(NC_("calendar heading",
                                                             "%A, %B %-d, %Y"));
        this._title.label = this._date.toLocaleFormat(dayFormat);
    }

    _reloadEvents() {
        if (this._eventSource.isLoading)
            return;

        this._reloading = true;

        let periodBegin = _getBeginningOfDay(this._date);
        let periodEnd = _getEndOfDay(this._date);
        let events = this._eventSource.getEvents(periodBegin, periodEnd);

        let ids = events.map(e => e.id);
        this._messageById.forEach((message, id) => {
            if (ids.includes(id))
                return;
            this._messageById.delete(id);
            this.removeMessage(message);
        });

        for (let i = 0; i < events.length; i++) {
            let event = events[i];

            let message = this._messageById.get(event.id);
            if (!message) {
                message = new EventMessage(event, this._date);
                this._messageById.set(event.id, message);
                this.addMessage(message, false);
            } else {
                this.moveMessage(message, i, false);
            }
        }

        this._reloading = false;
        this._sync();
    }

    _appInstalledChanged() {
        this._calendarApp = undefined;
        this._title.reactive = (this._getCalendarApp() != null);
    }

    _getCalendarApp() {
        if (this._calendarApp !== undefined)
            return this._calendarApp;

        let apps = Gio.AppInfo.get_recommended_for_type('text/calendar');
        if (apps && (apps.length > 0)) {
            let app = Gio.AppInfo.get_default_for_type('text/calendar', false);
            let defaultInRecommended = apps.some(a => a.equal(app));
            this._calendarApp = defaultInRecommended ? app : apps[0];
        } else {
            this._calendarApp = null;
        }
        return this._calendarApp;
    }

    _onTitleClicked() {
        Main.overview.hide();
        Main.panel.closeCalendar();

        let appInfo = this._getCalendarApp();
        if (app.get_id() == 'evolution.desktop') {
            let app = this._appSys.lookup_app('evolution-calendar.desktop');
            if (app)
                appInfo = app.app_info;
        }
        appInfo.launch([], global.create_app_launch_context(0, -1));
    }

    setDate(date) {
        super.setDate(date);
        this._updateTitle();
        this._reloadEvents();
    }

    _shouldShow() {
        return !this.empty || !isToday(this._date);
    }

    _sync() {
        if (this._reloading)
            return;

        super._sync();
    }
};

var NotificationSection =
class NotificationSection extends MessageList.MessageListSection {
    constructor() {
        super();

        this._sources = new Map();
        this._nUrgent = 0;

        Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
        Main.messageTray.getSources().forEach(source => {
            this._sourceAdded(Main.messageTray, source);
        });

        this.actor.connect('notify::mapped', this._onMapped.bind(this));
    }

    get allowed() {
        return Main.sessionMode.hasNotifications &&
               !Main.sessionMode.isGreeter;
    }

    _createTimeLabel(datetime) {
        let label = new St.Label({ style_class: 'event-time',
                                   x_align: Clutter.ActorAlign.START,
                                   y_align: Clutter.ActorAlign.END });
        label.connect('notify::mapped', () => {
            if (label.mapped)
                label.text = Util.formatTimeSpan(datetime);
        });
        return label;
    }

    _sourceAdded(tray, source) {
        let obj = {
            destroyId: 0,
            notificationAddedId: 0,
        };

        obj.destroyId = source.connect('destroy', source => {
            this._onSourceDestroy(source, obj);
        });
        obj.notificationAddedId = source.connect('notification-added',
                                                 this._onNotificationAdded.bind(this));

        this._sources.set(source, obj);
    }

    _onNotificationAdded(source, notification) {
        let message = new NotificationMessage(notification);
        message.setSecondaryActor(this._createTimeLabel(notification.datetime));

        let isUrgent = notification.urgency == MessageTray.Urgency.CRITICAL;

        let updatedId = notification.connect('updated', () => {
            message.setSecondaryActor(this._createTimeLabel(notification.datetime));
            this.moveMessage(message, isUrgent ? 0 : this._nUrgent, this.actor.mapped);
        });
        let destroyId = notification.connect('destroy', () => {
            notification.disconnect(destroyId);
            notification.disconnect(updatedId);
            if (isUrgent)
                this._nUrgent--;
        });

        if (isUrgent) {
            // Keep track of urgent notifications to keep them on top
            this._nUrgent++;
        } else if (this.mapped) {
            // Only acknowledge non-urgent notifications in case it
            // has important actions that are inaccessible when not
            // shown as banner
            notification.acknowledged = true;
        }

        let index = isUrgent ? 0 : this._nUrgent;
        this.addMessageAtIndex(message, index, this.actor.mapped);
    }

    _onSourceDestroy(source, obj) {
        source.disconnect(obj.destroyId);
        source.disconnect(obj.notificationAddedId);

        this._sources.delete(source);
    }

    _onMapped() {
        if (!this.actor.mapped)
            return;

        for (let message of this._messages.keys())
            if (message.notification.urgency != MessageTray.Urgency.CRITICAL)
                message.notification.acknowledged = true;
    }

    _shouldShow() {
        return !this.empty && isToday(this._date);
    }
};

var Placeholder = class Placeholder {
    constructor() {
        this.actor = new St.BoxLayout({ style_class: 'message-list-placeholder',
                                        vertical: true });

        this._date = new Date();

        let todayFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/no-notifications.svg');
        let otherFile = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/no-events.svg');
        this._todayIcon = new Gio.FileIcon({ file: todayFile });
        this._otherIcon = new Gio.FileIcon({ file: otherFile });

        this._icon = new St.Icon();
        this.actor.add_actor(this._icon);

        this._label = new St.Label();
        this.actor.add_actor(this._label);

        this._sync();
    }

    setDate(date) {
        if (sameDay(this._date, date))
            return;
        this._date = date;
        this._sync();
    }

    _sync() {
        let today = isToday(this._date);
        if (today && this._icon.gicon == this._todayIcon)
            return;
        if (!today && this._icon.gicon == this._otherIcon)
            return;

        if (today) {
            this._icon.gicon = this._todayIcon;
            this._label.text = _("No Notifications");
        } else {
            this._icon.gicon = this._otherIcon;
            this._label.text = _("No Events");
        }
    }
};

var CalendarMessageList = class CalendarMessageList {
    constructor() {
        this.actor = new St.Widget({ style_class: 'message-list',
                                     layout_manager: new Clutter.BinLayout(),
                                     x_expand: true, y_expand: true });

        this._placeholder = new Placeholder();
        this.actor.add_actor(this._placeholder.actor);

        let box = new St.BoxLayout({ vertical: true,
                                     x_expand: true, y_expand: true });
        this.actor.add_actor(box);

        this._scrollView = new St.ScrollView({ style_class: 'vfade',
                                               overlay_scrollbars: true,
                                               x_expand: true, y_expand: true,
                                               x_fill: true, y_fill: true });
        this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        box.add_actor(this._scrollView);

        this._clearButton = new St.Button({ style_class: 'message-list-clear-button button',
                                            label: _("Clear"),
                                            can_focus: true });
        this._clearButton.set_x_align(Clutter.ActorAlign.END);
        this._clearButton.connect('clicked', () => {
            let sections = [...this._sections.keys()];
            sections.forEach((s) => { s.clear(); });
        });
        box.add_actor(this._clearButton);

        this._sectionList = new St.BoxLayout({ style_class: 'message-list-sections',
                                               vertical: true,
                                               y_expand: true,
                                               y_align: Clutter.ActorAlign.START });
        this._scrollView.add_actor(this._sectionList);
        this._sections = new Map();

        this._mediaSection = new Mpris.MediaSection();
        this._addSection(this._mediaSection);

        this._notificationSection = new NotificationSection();
        this._addSection(this._notificationSection);

        this._eventsSection = new EventsSection();
        this._addSection(this._eventsSection);

        Main.sessionMode.connect('updated', this._sync.bind(this));
    }

    _addSection(section) {
        let obj = {
            destroyId: 0,
            visibleId:  0,
            emptyChangedId: 0,
            canClearChangedId: 0,
            keyFocusId: 0
        };
        obj.destroyId = section.actor.connect('destroy', () => {
            this._removeSection(section);
        });
        obj.visibleId = section.actor.connect('notify::visible',
                                              this._sync.bind(this));
        obj.emptyChangedId = section.connect('empty-changed',
                                             this._sync.bind(this));
        obj.canClearChangedId = section.connect('can-clear-changed',
                                                this._sync.bind(this));
        obj.keyFocusId = section.connect('key-focus-in',
                                         this._onKeyFocusIn.bind(this));

        this._sections.set(section, obj);
        this._sectionList.add_actor(section.actor);
        this._sync();
    }

    _removeSection(section) {
        let obj = this._sections.get(section);
        section.actor.disconnect(obj.destroyId);
        section.actor.disconnect(obj.visibleId);
        section.disconnect(obj.emptyChangedId);
        section.disconnect(obj.canClearChangedId);
        section.disconnect(obj.keyFocusId);

        this._sections.delete(section);
        this._sectionList.remove_actor(section.actor);
        this._sync();
    }

    _onKeyFocusIn(section, actor) {
        Util.ensureActorVisibleInScrollView(this._scrollView, actor);
    }

    _sync() {
        let sections = [...this._sections.keys()];
        let visible = sections.some(s => s.allowed);
        this.actor.visible = visible;
        if (!visible)
            return;

        let empty = sections.every(s => s.empty || !s.actor.visible);
        this._placeholder.actor.visible = empty;
        this._clearButton.visible = !empty;

        let canClear = sections.some(s => s.canClear && s.actor.visible);
        this._clearButton.reactive = canClear;
    }

    setEventSource(eventSource) {
        this._eventsSection.setEventSource(eventSource);
    }

    setDate(date) {
        for (let section of this._sections.keys())
            section.setDate(date);
        this._placeholder.setDate(date);
    }
};
(uuay)pageIndicators.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, St } = imports.gi;

const Tweener = imports.ui.tweener;
const { ANIMATION_TIME_OUT, ANIMATION_MAX_DELAY_OUT_FOR_ITEM, AnimationDirection } = imports.ui.iconGrid;

var INDICATORS_BASE_TIME = 0.25;
var INDICATORS_BASE_TIME_OUT = 0.125;
var INDICATORS_ANIMATION_DELAY = 0.125;
var INDICATORS_ANIMATION_DELAY_OUT = 0.0625;
var INDICATORS_ANIMATION_MAX_TIME = 0.75;
var SWITCH_TIME = 0.4;
var INDICATORS_ANIMATION_MAX_TIME_OUT =
    Math.min (SWITCH_TIME,
              ANIMATION_TIME_OUT + ANIMATION_MAX_DELAY_OUT_FOR_ITEM);

var ANIMATION_DELAY = 0.1;

var PageIndicators = GObject.registerClass({
    Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } }
}, class PageIndicators extends St.BoxLayout {
    _init(vertical = true) {
        super._init({ style_class: 'page-indicators',
                      vertical,
                      x_expand: true, y_expand: true,
                      x_align: vertical ? Clutter.ActorAlign.END : Clutter.ActorAlign.CENTER,
                      y_align: vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.END,
                      reactive: true,
                      clip_to_allocation: true });
        this._nPages = 0;
        this._currentPage = undefined;
        this._reactive = true;
        this._reactive = true;
    }

    vfunc_get_preferred_height(forWidth) {
        // We want to request the natural height of all our children as our
        // natural height, so we chain up to St.BoxLayout, but we only request 0
        // as minimum height, since it's not that important if some indicators
        // are not shown
        let [, natHeight] = super.vfunc_get_preferred_height(forWidth);
        return [0, natHeight];
    }

    setReactive(reactive) {
        let children = this.get_children();
        for (let i = 0; i < children.length; i++)
            children[i].reactive = reactive;

        this._reactive = reactive;
    }

    setNPages(nPages) {
        if (this._nPages == nPages)
            return;

        let diff = nPages - this._nPages;
        if (diff > 0) {
            for (let i = 0; i < diff; i++) {
                let pageIndex = this._nPages + i;
                let indicator = new St.Button({ style_class: 'page-indicator',
                                                button_mask: St.ButtonMask.ONE |
                                                             St.ButtonMask.TWO |
                                                             St.ButtonMask.THREE,
                                                toggle_mode: true,
                                                reactive: this._reactive,
                                                checked: pageIndex == this._currentPage });
                indicator.child = new St.Widget({ style_class: 'page-indicator-icon' });
                indicator.connect('clicked', () => {
                    this.emit('page-activated', pageIndex);
                });
                this.add_actor(indicator);
            }
        } else {
            let children = this.get_children().splice(diff);
            for (let i = 0; i < children.length; i++)
                children[i].destroy();
        }
        this._nPages = nPages;
        this.visible = (this._nPages > 1);
    }

    setCurrentPage(currentPage) {
        this._currentPage = currentPage;

        let children = this.get_children();
        for (let i = 0; i < children.length; i++)
            children[i].set_checked(i == this._currentPage);
    }
});

var AnimatedPageIndicators = GObject.registerClass(
class AnimatedPageIndicators extends PageIndicators {
    _init() {
        super._init(true);

        this.connect('notify::mapped', () => {
            this.animateIndicators(AnimationDirection.IN);
        });
    }

    animateIndicators(animationDirection) {
        if (!this.mapped)
            return;

        let children = this.get_children();
        if (children.length == 0)
            return;

        for (let i = 0; i < this._nPages; i++)
            Tweener.removeTweens(children[i]);

        let offset;
        if (this.get_text_direction() == Clutter.TextDirection.RTL)
            offset = -children[0].width;
        else
            offset = children[0].width;

        let isAnimationIn = animationDirection == AnimationDirection.IN;
        let delay = isAnimationIn ? INDICATORS_ANIMATION_DELAY :
                                    INDICATORS_ANIMATION_DELAY_OUT;
        let baseTime = isAnimationIn ? INDICATORS_BASE_TIME : INDICATORS_BASE_TIME_OUT;
        let totalAnimationTime = baseTime + delay * this._nPages;
        let maxTime = isAnimationIn ? INDICATORS_ANIMATION_MAX_TIME :
                                      INDICATORS_ANIMATION_MAX_TIME_OUT;
        if (totalAnimationTime > maxTime)
            delay -= (totalAnimationTime - maxTime) / this._nPages;

        for (let i = 0; i < this._nPages; i++) {
            children[i].translation_x = isAnimationIn ? offset : 0;
            Tweener.addTween(children[i], {
                translation_x: isAnimationIn ? 0 : offset,
                time: baseTime + delay * i,
                transition: 'easeInOutQuad',
                delay: isAnimationIn ? ANIMATION_DELAY : 0
            });
        }
    }
});
(uuay)bluetooth.js]// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GnomeBluetooth } = imports.gi;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const { loadInterfaceXML } = imports.misc.fileUtils;

const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill';

const RfkillManagerInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Rfkill');
const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface);

const HAD_BLUETOOTH_DEVICES_SETUP = 'had-bluetooth-devices-setup';

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'bluetooth-active-symbolic';
        this._hadSetupDevices = global.settings.get_boolean(HAD_BLUETOOTH_DEVICES_SETUP);

        this._proxy = new RfkillManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
                                             (proxy, error) => {
                                                 if (error) {
                                                     log(error.message);
                                                     return;
                                                 }

                                                 this._sync();
                                             });
        this._proxy.connect('g-properties-changed', this._sync.bind(this));

        this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true);
        this._item.icon.icon_name = 'bluetooth-active-symbolic';

        this._toggleItem = new PopupMenu.PopupMenuItem('');
        this._toggleItem.connect('activate', () => {
            this._proxy.BluetoothAirplaneMode = !this._proxy.BluetoothAirplaneMode;
        });
        this._item.menu.addMenuItem(this._toggleItem);

        this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop');
        this.menu.addMenuItem(this._item);

        this._client = new GnomeBluetooth.Client();
        this._model = this._client.get_model();
        this._model.connect('row-changed', this._sync.bind(this));
        this._model.connect('row-deleted', this._sync.bind(this));
        this._model.connect('row-inserted', this._sync.bind(this));
        Main.sessionMode.connect('updated', this._sync.bind(this));
        this._sync();
    }

    _getDefaultAdapter() {
        let [ret, iter] = this._model.get_iter_first();
        while (ret) {
            let isDefault = this._model.get_value(iter,
                                                  GnomeBluetooth.Column.DEFAULT);
            let isPowered = this._model.get_value(iter,
                                                  GnomeBluetooth.Column.POWERED);
            if (isDefault && isPowered)
                return iter;
            ret = this._model.iter_next(iter);
        }
        return null;
    }

    // nDevices is the number of devices setup for the current default
    // adapter if one exists and is powered. If unpowered or unavailable,
    // nDevice is "1" if it had setup devices associated to it the last
    // time it was seen, and "-1" if not.
    //
    // nConnectedDevices is the number of devices connected to the default
    // adapter if one exists and is powered, or -1 if it's not available.
    _getNDevices() {
        let adapter = this._getDefaultAdapter();
        if (!adapter)
            return [ this._hadSetupDevices ? 1 : -1, -1 ];

        let nConnectedDevices = 0;
        let nDevices = 0;
        let [ret, iter] = this._model.iter_children(adapter);
        while (ret) {
            let isConnected = this._model.get_value(iter,
                                                    GnomeBluetooth.Column.CONNECTED);
            if (isConnected)
                nConnectedDevices++;

            let isPaired = this._model.get_value(iter,
                                                 GnomeBluetooth.Column.PAIRED);
            let isTrusted = this._model.get_value(iter,
                                                  GnomeBluetooth.Column.TRUSTED);
            if (isPaired || isTrusted)
                nDevices++;
            ret = this._model.iter_next(iter);
        }

        if (this._hadSetupDevices != (nDevices > 0)) {
            this._hadSetupDevices = !this._hadSetupDevices;
            global.settings.set_boolean(HAD_BLUETOOTH_DEVICES_SETUP, this._hadSetupDevices);
        }

        return [ nDevices, nConnectedDevices];
    }

    _sync() {
        let [ nDevices, nConnectedDevices ] = this._getNDevices();
        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;

        this.menu.setSensitive(sensitive);
        this._indicator.visible = nConnectedDevices > 0;

        // Remember if there were setup devices and show the menu
        // if we've seen setup devices and we're not hard blocked
        if (nDevices > 0)
            this._item.actor.visible = !this._proxy.BluetoothHardwareAirplaneMode;
        else
            this._item.actor.visible = this._proxy.BluetoothHasAirplaneMode && !this._proxy.BluetoothAirplaneMode;

        if (nConnectedDevices > 0)
            /* Translators: this is the number of connected bluetooth devices */
            this._item.label.text = ngettext("%d Connected", "%d Connected", nConnectedDevices).format(nConnectedDevices);
        else if (nConnectedDevices == -1)
            this._item.label.text = _("Off");
        else
            this._item.label.text = _("On");

        this._toggleItem.label.text = this._proxy.BluetoothAirplaneMode ? _("Turn On") : _("Turn Off");
    }
};
(uuay)fileUtils.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib } = imports.gi;
const Config = imports.misc.config;

function collectFromDatadirs(subdir, includeUserDir, processFile) {
    let dataDirs = GLib.get_system_data_dirs();
    if (includeUserDir)
        dataDirs.unshift(GLib.get_user_data_dir());

    for (let i = 0; i < dataDirs.length; i++) {
        let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', subdir]);
        let dir = Gio.File.new_for_path(path);

        let fileEnum;
        try {
            fileEnum = dir.enumerate_children('standard::name,standard::type',
                                              Gio.FileQueryInfoFlags.NONE, null);
        } catch (e) {
            fileEnum = null;
        }
        if (fileEnum != null) {
            let info;
            while ((info = fileEnum.next_file(null)))
                processFile(fileEnum.get_child(info), info);
        }
    }
}

function deleteGFile(file) {
    // Work around 'delete' being a keyword in JS.
    return file['delete'](null);
}

function recursivelyDeleteDir(dir, deleteParent) {
    let children = dir.enumerate_children('standard::name,standard::type',
                                          Gio.FileQueryInfoFlags.NONE, null);

    let info, child;
    while ((info = children.next_file(null)) != null) {
        let type = info.get_file_type();
        let child = dir.get_child(info.get_name());
        if (type == Gio.FileType.REGULAR)
            deleteGFile(child);
        else if (type == Gio.FileType.DIRECTORY)
            recursivelyDeleteDir(child, true);
    }

    if (deleteParent)
        deleteGFile(dir);
}

function recursivelyMoveDir(srcDir, destDir) {
    let children = srcDir.enumerate_children('standard::name,standard::type',
                                             Gio.FileQueryInfoFlags.NONE, null);

    if (!destDir.query_exists(null))
        destDir.make_directory_with_parents(null);

    let info, child;
    while ((info = children.next_file(null)) != null) {
        let type = info.get_file_type();
        let srcChild = srcDir.get_child(info.get_name());
        let destChild = destDir.get_child(info.get_name());
        if (type == Gio.FileType.REGULAR)
            srcChild.move(destChild, Gio.FileCopyFlags.NONE, null, null);
        else if (type == Gio.FileType.DIRECTORY)
            recursivelyMoveDir(srcChild, destChild);
    }
}

let _ifaceResource = null;
function loadInterfaceXML(iface) {
    if (!_ifaceResource) {
        // don't use global.datadir so the method is usable from tests/tools
        let dir = GLib.getenv ('GNOME_SHELL_DATADIR') || Config.PKGDATADIR;
        let path = dir + '/gnome-shell-dbus-interfaces.gresource';
        _ifaceResource = Gio.Resource.load(path);
        _ifaceResource._register();
    }

    let xml = null;
    let uri = 'resource:///org/gnome/shell/dbus-interfaces/' + iface + '.xml';
    let f = Gio.File.new_for_uri(uri);

    try {
        let [ok, bytes] = f.load_contents(null);
        if (bytes instanceof Uint8Array)
            xml = imports.byteArray.toString(bytes)
        else
            xml = bytes.toString();
    } catch (e) {
        log('Failed to load D-Bus interface ' + iface);
    }

    return xml;
}
(uuay)dash.js�{// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const AppDisplay = imports.ui.appDisplay;
const AppFavorites = imports.ui.appFavorites;
const DND = imports.ui.dnd;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var DASH_ANIMATION_TIME = 0.2;
var DASH_ITEM_LABEL_SHOW_TIME = 0.15;
var DASH_ITEM_LABEL_HIDE_TIME = 0.1;
var DASH_ITEM_HOVER_TIMEOUT = 300;

function getAppFromSource(source) {
    if (source instanceof AppDisplay.AppIcon) {
        return source.app;
    } else {
        return null;
    }
}

// A container like StBin, but taking the child's scale into account
// when requesting a size
var DashItemContainer = GObject.registerClass(
class DashItemContainer extends St.Widget {
    _init() {
        super._init({ style_class: 'dash-item-container',
                      pivot_point: new Clutter.Point({ x: .5, y: .5 }),
                      x_expand: true,
                      x_align: Clutter.ActorAlign.CENTER });

        this._labelText = "";
        this.label = new St.Label({ style_class: 'dash-label'});
        this.label.hide();
        Main.layoutManager.addChrome(this.label);
        this.label_actor = this.label;

        this.child = null;
        this._childScale = 0;
        this._childOpacity = 0;
        this.animatingOut = false;

        this.connect('destroy', () => {
            if (this.child != null)
                this.child.destroy();
            this.label.destroy();
        });
    }

    vfunc_get_preferred_height(forWidth) {
        let themeNode = this.get_theme_node();
        forWidth = themeNode.adjust_for_width(forWidth);
        let [minHeight, natHeight] = super.vfunc_get_preferred_height(forWidth);
        return themeNode.adjust_preferred_height(minHeight * this.scale_y,
                                                 natHeight * this.scale_y);
    }

    vfunc_get_preferred_width(forHeight) {
        let themeNode = this.get_theme_node();
        forHeight = themeNode.adjust_for_height(forHeight);
        let [minWidth, natWidth] = super.vfunc_get_preferred_width(forHeight);
        return themeNode.adjust_preferred_width(minWidth * this.scale_x,
                                                natWidth * this.scale_x);
    }

    showLabel() {
        if (!this._labelText)
            return;

        this.label.set_text(this._labelText);
        this.label.opacity = 0;
        this.label.show();

        let [stageX, stageY] = this.get_transformed_position();

        let itemHeight = this.allocation.y2 - this.allocation.y1;

        let labelHeight = this.label.get_height();
        let yOffset = Math.floor((itemHeight - labelHeight) / 2)

        let y = stageY + yOffset;

        let node = this.label.get_theme_node();
        let xOffset = node.get_length('-x-offset');

        let x;
        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
            x = stageX - this.label.get_width() - xOffset;
        else
            x = stageX + this.get_width() + xOffset;

        this.label.set_position(x, y);
        Tweener.addTween(this.label,
                         { opacity: 255,
                           time: DASH_ITEM_LABEL_SHOW_TIME,
                           transition: 'easeOutQuad',
                         });
    }

    setLabelText(text) {
        this._labelText = text;
        this.child.accessible_name = text;
    }

    hideLabel() {
        Tweener.addTween(this.label,
                         { opacity: 0,
                           time: DASH_ITEM_LABEL_HIDE_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this.label.hide();
                           }
                         });
    }

    setChild(actor) {
        if (this.child == actor)
            return;

        this.destroy_all_children();

        this.child = actor;
        this.add_actor(this.child);

        this.set_scale(this._childScale, this._childScale);
        this.set_opacity(this._childOpacity);
    }

    show(animate) {
        if (this.child == null)
            return;

        let time = animate ? DASH_ANIMATION_TIME : 0;
        Tweener.addTween(this,
                         { childScale: 1.0,
                           childOpacity: 255,
                           time: time,
                           transition: 'easeOutQuad'
                         });
    }

    animateOutAndDestroy() {
        this.label.hide();

        if (this.child == null) {
            this.destroy();
            return;
        }

        this.animatingOut = true;
        Tweener.addTween(this,
                         { childScale: 0.0,
                           childOpacity: 0,
                           time: DASH_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this.destroy();
                           }
                         });
    }

    set childScale(scale) {
        this._childScale = scale;

        this.set_scale(scale, scale);
        this.queue_relayout();
    }

    get childScale() {
        return this._childScale;
    }

    set childOpacity(opacity) {
        this._childOpacity = opacity;

        this.set_opacity(opacity);
        this.queue_redraw();
    }

    get childOpacity() {
        return this._childOpacity;
    }
});

var ShowAppsIcon = GObject.registerClass(
class ShowAppsIcon extends DashItemContainer {
    _init() {
        super._init();

        this.toggleButton = new St.Button({ style_class: 'show-apps',
                                            track_hover: true,
                                            can_focus: true,
                                            toggle_mode: true });
        this._iconActor = null;
        this.icon = new IconGrid.BaseIcon(_("Show Applications"),
                                           { setSizeManually: true,
                                             showLabel: false,
                                             createIcon: this._createIcon.bind(this) });
        this.toggleButton.add_actor(this.icon);
        this.toggleButton._delegate = this;

        this.setChild(this.toggleButton);
        this.setDragApp(null);
    }

    _createIcon(size) {
        this._iconActor = new St.Icon({ icon_name: 'view-app-grid-symbolic',
                                        icon_size: size,
                                        style_class: 'show-apps-icon',
                                        track_hover: true });
        return this._iconActor;
    }

    _canRemoveApp(app) {
        if (app == null)
            return false;

        if (!global.settings.is_writable('favorite-apps'))
            return false;

        let id = app.get_id();
        let isFavorite = AppFavorites.getAppFavorites().isFavorite(id);
        return isFavorite;
    }

    setDragApp(app) {
        let canRemove = this._canRemoveApp(app);

        this.toggleButton.set_hover(canRemove);
        if (this._iconActor)
            this._iconActor.set_hover(canRemove);

        if (canRemove)
            this.setLabelText(_("Remove from Favorites"));
        else
            this.setLabelText(_("Show Applications"));
    }

    handleDragOver(source, actor, x, y, time) {
        if (!this._canRemoveApp(getAppFromSource(source)))
            return DND.DragMotionResult.NO_DROP;

        return DND.DragMotionResult.MOVE_DROP;
    }

    acceptDrop(source, actor, x, y, time) {
        let app = getAppFromSource(source);
        if (!this._canRemoveApp(app))
            return false;

        let id = app.get_id();

        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            AppFavorites.getAppFavorites().removeFavorite(id);
            return false;
        });

        return true;
    }
});

var DragPlaceholderItem = GObject.registerClass(
class DragPlaceholderItem extends DashItemContainer {
    _init() {
        super._init();
        this.setChild(new St.Bin({ style_class: 'placeholder' }));
    }
});

var EmptyDropTargetItem = GObject.registerClass(
class EmptyDropTargetItem extends DashItemContainer {
    _init() {
        super._init();
        this.setChild(new St.Bin({ style_class: 'empty-dash-drop-target' }));
    }
});

var DashActor = GObject.registerClass(
class DashActor extends St.Widget {
    _init() {
        let layout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.VERTICAL });
        super._init({ name: 'dash',
                      layout_manager: layout,
                      clip_to_allocation: true });
    }

    vfunc_allocate(box, flags) {
        let contentBox = this.get_theme_node().get_content_box(box);
        let availWidth = contentBox.x2 - contentBox.x1;

        this.set_allocation(box, flags);

        let [appIcons, showAppsButton] = this.get_children();
        let [showAppsMinHeight, showAppsNatHeight] = showAppsButton.get_preferred_height(availWidth);

        let childBox = new Clutter.ActorBox();
        childBox.x1 = contentBox.x1;
        childBox.y1 = contentBox.y1;
        childBox.x2 = contentBox.x2;
        childBox.y2 = contentBox.y2 - showAppsNatHeight;
        appIcons.allocate(childBox, flags);

        childBox.y1 = contentBox.y2 - showAppsNatHeight;
        childBox.y2 = contentBox.y2;
        showAppsButton.allocate(childBox, flags);
    }

    vfunc_get_preferred_height(forWidth) {
        // We want to request the natural height of all our children
        // as our natural height, so we chain up to StWidget (which
        // then calls BoxLayout), but we only request the showApps
        // button as the minimum size

        let [, natHeight] = super.vfunc_get_preferred_height(forWidth);

        let themeNode = this.get_theme_node();
        let adjustedForWidth = themeNode.adjust_for_width(forWidth);
        let [, showAppsButton] = this.get_children();
        let [minHeight, ] = showAppsButton.get_preferred_height(adjustedForWidth);
        [minHeight, ] = themeNode.adjust_preferred_height(minHeight, natHeight);

        return [minHeight, natHeight];
    }
});

const baseIconSizes = [ 16, 22, 24, 32, 48, 64 ];

var Dash = class Dash {
    constructor() {
        this._maxHeight = -1;
        this.iconSize = 64;
        this._shownInitially = false;

        this._dragPlaceholder = null;
        this._dragPlaceholderPos = -1;
        this._animatingPlaceholdersCount = 0;
        this._showLabelTimeoutId = 0;
        this._resetHoverTimeoutId = 0;
        this._labelShowing = false;

        this._container = new DashActor();
        this._box = new St.BoxLayout({ vertical: true,
                                       clip_to_allocation: true });
        this._box._delegate = this;
        this._container.add_actor(this._box);
        this._container.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);

        this._showAppsIcon = new ShowAppsIcon();
        this._showAppsIcon.childScale = 1;
        this._showAppsIcon.childOpacity = 255;
        this._showAppsIcon.icon.setIconSize(this.iconSize);
        this._hookUpLabel(this._showAppsIcon);

        this.showAppsButton = this._showAppsIcon.toggleButton;

        this._container.add_actor(this._showAppsIcon);

        this.actor = new St.Bin({ child: this._container });
        this.actor.connect('notify::height', () => {
            if (this._maxHeight != this.actor.height)
                this._queueRedisplay();
            this._maxHeight = this.actor.height;
        });

        this._workId = Main.initializeDeferredWork(this._box, this._redisplay.bind(this));

        this._appSystem = Shell.AppSystem.get_default();

        this._appSystem.connect('installed-changed', () => {
            AppFavorites.getAppFavorites().reload();
            this._queueRedisplay();
        });
        AppFavorites.getAppFavorites().connect('changed', this._queueRedisplay.bind(this));
        this._appSystem.connect('app-state-changed', this._queueRedisplay.bind(this));

        Main.overview.connect('item-drag-begin',
                              this._onDragBegin.bind(this));
        Main.overview.connect('item-drag-end',
                              this._onDragEnd.bind(this));
        Main.overview.connect('item-drag-cancelled',
                              this._onDragCancelled.bind(this));

        // Translators: this is the name of the dock/favorites area on
        // the left of the overview
        Main.ctrlAltTabManager.addGroup(this.actor, _("Dash"), 'user-bookmarks-symbolic');
    }

    _onDragBegin() {
        this._dragCancelled = false;
        this._dragMonitor = {
            dragMotion: this._onDragMotion.bind(this)
        };
        DND.addDragMonitor(this._dragMonitor);

        if (this._box.get_n_children() == 0) {
            this._emptyDropTarget = new EmptyDropTargetItem();
            this._box.insert_child_at_index(this._emptyDropTarget, 0);
            this._emptyDropTarget.show(true);
        }
    }

    _onDragCancelled() {
        this._dragCancelled = true;
        this._endDrag();
    }

    _onDragEnd() {
        if (this._dragCancelled)
            return;

        this._endDrag();
    }

    _endDrag() {
        this._clearDragPlaceholder();
        this._clearEmptyDropTarget();
        this._showAppsIcon.setDragApp(null);
        DND.removeDragMonitor(this._dragMonitor);
    }

    _onDragMotion(dragEvent) {
        let app = getAppFromSource(dragEvent.source);
        if (app == null)
            return DND.DragMotionResult.CONTINUE;

        let showAppsHovered =
                this._showAppsIcon.contains(dragEvent.targetActor);

        if (!this._box.contains(dragEvent.targetActor) || showAppsHovered)
            this._clearDragPlaceholder();

        if (showAppsHovered)
            this._showAppsIcon.setDragApp(app);
        else
            this._showAppsIcon.setDragApp(null);

        return DND.DragMotionResult.CONTINUE;
    }

    _appIdListToHash(apps) {
        let ids = {};
        for (let i = 0; i < apps.length; i++)
            ids[apps[i].get_id()] = apps[i];
        return ids;
    }

    _queueRedisplay() {
        Main.queueDeferredWork(this._workId);
    }

    _hookUpLabel(item, appIcon) {
        item.child.connect('notify::hover', () => {
            this._syncLabel(item, appIcon);
        });

        let id = Main.overview.connect('hiding', () => {
            this._labelShowing = false;
            item.hideLabel();
        });
        item.child.connect('destroy', () => {
            Main.overview.disconnect(id);
        });

        if (appIcon) {
            appIcon.connect('sync-tooltip', () => {
                this._syncLabel(item, appIcon);
            });
        }
    }

    _createAppItem(app) {
        let appIcon = new AppDisplay.AppIcon(app,
                                             { setSizeManually: true,
                                               showLabel: false });
        if (appIcon._draggable) {
            appIcon._draggable.connect('drag-begin',
                                       () => {
                                           appIcon.actor.opacity = 50;
                                       });
            appIcon._draggable.connect('drag-end',
                                       () => {
                                           appIcon.actor.opacity = 255;
                                       });
        }

        appIcon.connect('menu-state-changed',
                        (appIcon, opened) => {
                            this._itemMenuStateChanged(item, opened);
                        });

        let item = new DashItemContainer();
        item.setChild(appIcon.actor);

        // Override default AppIcon label_actor, now the
        // accessible_name is set at DashItemContainer.setLabelText
        appIcon.actor.label_actor = null;
        item.setLabelText(app.get_name());

        appIcon.icon.setIconSize(this.iconSize);
        this._hookUpLabel(item, appIcon);

        return item;
    }

    _itemMenuStateChanged(item, opened) {
        // When the menu closes, it calls sync_hover, which means
        // that the notify::hover handler does everything we need to.
        if (opened) {
            if (this._showLabelTimeoutId > 0) {
                Mainloop.source_remove(this._showLabelTimeoutId);
                this._showLabelTimeoutId = 0;
            }

            item.hideLabel();
        }
    }

    _syncLabel(item, appIcon) {
        let shouldShow = appIcon ? appIcon.shouldShowTooltip() : item.child.get_hover();

        if (shouldShow) {
            if (this._showLabelTimeoutId == 0) {
                let timeout = this._labelShowing ? 0 : DASH_ITEM_HOVER_TIMEOUT;
                this._showLabelTimeoutId = Mainloop.timeout_add(timeout,
                    () => {
                        this._labelShowing = true;
                        item.showLabel();
                        this._showLabelTimeoutId = 0;
                        return GLib.SOURCE_REMOVE;
                    });
                GLib.Source.set_name_by_id(this._showLabelTimeoutId, '[gnome-shell] item.showLabel');
                if (this._resetHoverTimeoutId > 0) {
                    Mainloop.source_remove(this._resetHoverTimeoutId);
                    this._resetHoverTimeoutId = 0;
                }
            }
        } else {
            if (this._showLabelTimeoutId > 0)
                Mainloop.source_remove(this._showLabelTimeoutId);
            this._showLabelTimeoutId = 0;
            item.hideLabel();
            if (this._labelShowing) {
                this._resetHoverTimeoutId = Mainloop.timeout_add(DASH_ITEM_HOVER_TIMEOUT,
                    () => {
                        this._labelShowing = false;
                        this._resetHoverTimeoutId = 0;
                        return GLib.SOURCE_REMOVE;
                    });
                GLib.Source.set_name_by_id(this._resetHoverTimeoutId, '[gnome-shell] this._labelShowing');
            }
        }
    }

    _adjustIconSize() {
        // For the icon size, we only consider children which are "proper"
        // icons (i.e. ignoring drag placeholders) and which are not
        // animating out (which means they will be destroyed at the end of
        // the animation)
        let iconChildren = this._box.get_children().filter(actor => {
            return actor.child &&
                   actor.child._delegate &&
                   actor.child._delegate.icon &&
                   !actor.animatingOut;
        });

        iconChildren.push(this._showAppsIcon);

        if (this._maxHeight == -1)
            return;

        let themeNode = this._container.get_theme_node();
        let maxAllocation = new Clutter.ActorBox({ x1: 0, y1: 0,
                                                   x2: 42 /* whatever */,
                                                   y2: this._maxHeight });
        let maxContent = themeNode.get_content_box(maxAllocation);
        let availHeight = maxContent.y2 - maxContent.y1;
        let spacing = themeNode.get_length('spacing');

        let firstButton = iconChildren[0].child;
        let firstIcon = firstButton._delegate.icon;

        // Enforce valid spacings during the size request
        firstIcon.icon.ensure_style();
        let [, iconHeight] = firstIcon.icon.get_preferred_height(-1);
        let [, buttonHeight] = firstButton.get_preferred_height(-1);

        // Subtract icon padding and box spacing from the available height
        availHeight -= iconChildren.length * (buttonHeight - iconHeight) +
                       (iconChildren.length - 1) * spacing;

        let availSize = availHeight / iconChildren.length;

        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        let iconSizes = baseIconSizes.map(s => s * scaleFactor);

        let newIconSize = baseIconSizes[0];
        for (let i = 0; i < iconSizes.length; i++) {
            if (iconSizes[i] < availSize)
                newIconSize = baseIconSizes[i];
        }

        if (newIconSize == this.iconSize)
            return;

        let oldIconSize = this.iconSize;
        this.iconSize = newIconSize;
        this.emit('icon-size-changed');

        let scale = oldIconSize / newIconSize;
        for (let i = 0; i < iconChildren.length; i++) {
            let icon = iconChildren[i].child._delegate.icon;

            // Set the new size immediately, to keep the icons' sizes
            // in sync with this.iconSize
            icon.setIconSize(this.iconSize);

            // Don't animate the icon size change when the overview
            // is transitioning, not visible or when initially filling
            // the dash
            if (!Main.overview.visible || Main.overview.animationInProgress ||
                !this._shownInitially)
                continue;

            let [targetWidth, targetHeight] = icon.icon.get_size();

            // Scale the icon's texture to the previous size and
            // tween to the new size
            icon.icon.set_size(icon.icon.width * scale,
                               icon.icon.height * scale);

            Tweener.addTween(icon.icon,
                             { width: targetWidth,
                               height: targetHeight,
                               time: DASH_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                             });
        }
    }

    _redisplay() {
        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();

        let running = this._appSystem.get_running();

        let children = this._box.get_children().filter(actor => {
                return actor.child &&
                       actor.child._delegate &&
                       actor.child._delegate.app;
            });
        // Apps currently in the dash
        let oldApps = children.map(actor => actor.child._delegate.app);
        // Apps supposed to be in the dash
        let newApps = [];

        for (let id in favorites)
            newApps.push(favorites[id]);

        for (let i = 0; i < running.length; i++) {
            let app = running[i];
            if (app.get_id() in favorites)
                continue;
            newApps.push(app);
        }

        // Figure out the actual changes to the list of items; we iterate
        // over both the list of items currently in the dash and the list
        // of items expected there, and collect additions and removals.
        // Moves are both an addition and a removal, where the order of
        // the operations depends on whether we encounter the position
        // where the item has been added first or the one from where it
        // was removed.
        // There is an assumption that only one item is moved at a given
        // time; when moving several items at once, everything will still
        // end up at the right position, but there might be additional
        // additions/removals (e.g. it might remove all the launchers
        // and add them back in the new order even if a smaller set of
        // additions and removals is possible).
        // If above assumptions turns out to be a problem, we might need
        // to use a more sophisticated algorithm, e.g. Longest Common
        // Subsequence as used by diff.
        let addedItems = [];
        let removedActors = [];

        let newIndex = 0;
        let oldIndex = 0;
        while (newIndex < newApps.length || oldIndex < oldApps.length) {
            let oldApp = oldApps.length > oldIndex ? oldApps[oldIndex] : null;
            let newApp = newApps.length > newIndex ? newApps[newIndex] : null;

            // No change at oldIndex/newIndex
            if (oldApp == newApp) {
                oldIndex++;
                newIndex++;
                continue;
            }

            // App removed at oldIndex
            if (oldApp && newApps.indexOf(oldApp) == -1) {
                removedActors.push(children[oldIndex]);
                oldIndex++;
                continue;
            }

            // App added at newIndex
            if (newApp && oldApps.indexOf(newApp) == -1) {
                addedItems.push({ app: newApp,
                                  item: this._createAppItem(newApp),
                                  pos: newIndex });
                newIndex++;
                continue;
            }

            // App moved
            let nextApp = newApps.length > newIndex + 1 ? newApps[newIndex + 1]
                                                        : null;
            let insertHere = nextApp && nextApp == oldApp;
            let alreadyRemoved = removedActors.reduce((result, actor) => {
                let removedApp = actor.child._delegate.app;
                return result || removedApp == newApp;
            }, false);

            if (insertHere || alreadyRemoved) {
                let newItem = this._createAppItem(newApp);
                addedItems.push({ app: newApp,
                                  item: newItem,
                                  pos: newIndex + removedActors.length });
                newIndex++;
            } else {
                removedActors.push(children[oldIndex]);
                oldIndex++;
            }
        }

        for (let i = 0; i < addedItems.length; i++)
            this._box.insert_child_at_index(addedItems[i].item,
                                            addedItems[i].pos);

        for (let i = 0; i < removedActors.length; i++) {
            let item = removedActors[i];

            // Don't animate item removal when the overview is transitioning
            // or hidden
            if (Main.overview.visible && !Main.overview.animationInProgress)
                item.animateOutAndDestroy();
            else
                item.destroy();
        }

        this._adjustIconSize();

        // Skip animations on first run when adding the initial set
        // of items, to avoid all items zooming in at once

        let animate = this._shownInitially && Main.overview.visible &&
            !Main.overview.animationInProgress;

        if (!this._shownInitially)
            this._shownInitially = true;

        for (let i = 0; i < addedItems.length; i++) {
            addedItems[i].item.show(animate);
        }

        // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=692744
        // Without it, StBoxLayout may use a stale size cache
        this._box.queue_relayout();
    }

    _clearDragPlaceholder() {
        if (this._dragPlaceholder) {
            this._animatingPlaceholdersCount++;
            this._dragPlaceholder.animateOutAndDestroy();
            this._dragPlaceholder.connect('destroy', () => {
                this._animatingPlaceholdersCount--;
            });
            this._dragPlaceholder = null;
        }
        this._dragPlaceholderPos = -1;
    }

    _clearEmptyDropTarget() {
        if (this._emptyDropTarget) {
            this._emptyDropTarget.animateOutAndDestroy();
            this._emptyDropTarget = null;
        }
    }

    handleDragOver(source, actor, x, y, time) {
        let app = getAppFromSource(source);

        // Don't allow favoriting of transient apps
        if (app == null || app.is_window_backed())
            return DND.DragMotionResult.NO_DROP;

        if (!global.settings.is_writable('favorite-apps'))
            return DND.DragMotionResult.NO_DROP;

        let favorites = AppFavorites.getAppFavorites().getFavorites();
        let numFavorites = favorites.length;

        let favPos = favorites.indexOf(app);

        let children = this._box.get_children();
        let numChildren = children.length;
        let boxHeight = this._box.height;

        // Keep the placeholder out of the index calculation; assuming that
        // the remove target has the same size as "normal" items, we don't
        // need to do the same adjustment there.
        if (this._dragPlaceholder) {
            boxHeight -= this._dragPlaceholder.height;
            numChildren--;
        }

        let pos;
        if (!this._emptyDropTarget)
            pos = Math.floor(y * numChildren / boxHeight);
        else
            pos = 0; // always insert at the top when dash is empty

        if (pos != this._dragPlaceholderPos && pos <= numFavorites && this._animatingPlaceholdersCount == 0) {
            this._dragPlaceholderPos = pos;

            // Don't allow positioning before or after self
            if (favPos != -1 && (pos == favPos || pos == favPos + 1)) {
                this._clearDragPlaceholder();
                return DND.DragMotionResult.CONTINUE;
            }

            // If the placeholder already exists, we just move
            // it, but if we are adding it, expand its size in
            // an animation
            let fadeIn;
            if (this._dragPlaceholder) {
                this._dragPlaceholder.destroy();
                fadeIn = false;
            } else {
                fadeIn = true;
            }

            this._dragPlaceholder = new DragPlaceholderItem();
            this._dragPlaceholder.child.set_width (this.iconSize);
            this._dragPlaceholder.child.set_height (this.iconSize / 2);
            this._box.insert_child_at_index(this._dragPlaceholder,
                                            this._dragPlaceholderPos);
            this._dragPlaceholder.show(fadeIn);
        }

        // Remove the drag placeholder if we are not in the
        // "favorites zone"
        if (pos > numFavorites)
            this._clearDragPlaceholder();

        if (!this._dragPlaceholder)
            return DND.DragMotionResult.NO_DROP;

        let srcIsFavorite = (favPos != -1);

        if (srcIsFavorite)
            return DND.DragMotionResult.MOVE_DROP;

        return DND.DragMotionResult.COPY_DROP;
    }

    // Draggable target interface
    acceptDrop(source, actor, x, y, time) {
        let app = getAppFromSource(source);

        // Don't allow favoriting of transient apps
        if (app == null || app.is_window_backed()) {
            return false;
        }

        if (!global.settings.is_writable('favorite-apps'))
            return false;

        let id = app.get_id();

        let favorites = AppFavorites.getAppFavorites().getFavoriteMap();

        let srcIsFavorite = (id in favorites);

        let favPos = 0;
        let children = this._box.get_children();
        for (let i = 0; i < this._dragPlaceholderPos; i++) {
            if (this._dragPlaceholder &&
                children[i] == this._dragPlaceholder)
                continue;

            let childId = children[i].child._delegate.app.get_id();
            if (childId == id)
                continue;
            if (childId in favorites)
                favPos++;
        }

        // No drag placeholder means we don't wan't to favorite the app
        // and we are dragging it to its original position
        if (!this._dragPlaceholder)
            return true;

        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            let appFavorites = AppFavorites.getAppFavorites();
            if (srcIsFavorite)
                appFavorites.moveFavoriteToPos(id, favPos);
            else
                appFavorites.addFavoriteAtPos(id, favPos);
            return false;
        });

        return true;
    }
};
Signals.addSignalMethods(Dash.prototype);
(uuay)loginManager.jsE// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GLib, Gio } = imports.gi;
const Signals = imports.signals;

const { loadInterfaceXML } = imports.misc.fileUtils;

const SystemdLoginManagerIface = loadInterfaceXML('org.freedesktop.login1.Manager');
const SystemdLoginSessionIface = loadInterfaceXML('org.freedesktop.login1.Session');
const SystemdLoginUserIface = loadInterfaceXML('org.freedesktop.login1.User');

const SystemdLoginManager = Gio.DBusProxy.makeProxyWrapper(SystemdLoginManagerIface);
const SystemdLoginSession = Gio.DBusProxy.makeProxyWrapper(SystemdLoginSessionIface);
const SystemdLoginUser = Gio.DBusProxy.makeProxyWrapper(SystemdLoginUserIface);

function haveSystemd() {
    return GLib.access("/run/systemd/seats", 0) >= 0;
}

function versionCompare(required, reference) {
    required = required.split('.');
    reference = reference.split('.');

    for (let i = 0; i < required.length; i++) {
        let requiredInt = parseInt(required[i]);
        let referenceInt = parseInt(reference[i]);
        if (requiredInt != referenceInt)
            return requiredInt < referenceInt;
    }

    return true;
}

function canLock() {
    try {
        let params = GLib.Variant.new('(ss)', ['org.gnome.DisplayManager.Manager', 'Version']);
        let result = Gio.DBus.system.call_sync('org.gnome.DisplayManager',
                                               '/org/gnome/DisplayManager/Manager',
                                               'org.freedesktop.DBus.Properties',
                                               'Get', params, null,
                                               Gio.DBusCallFlags.NONE,
                                               -1, null);

        let version = result.deep_unpack()[0].deep_unpack();
        return haveSystemd() && versionCompare('3.5.91', version);
    } catch(e) {
        return false;
    }
}

let _loginManager = null;

/**
 * LoginManager:
 * An abstraction over systemd/logind and ConsoleKit.
 *
 */
function getLoginManager() {
    if (_loginManager == null) {
        if (haveSystemd())
            _loginManager = new LoginManagerSystemd();
        else
            _loginManager = new LoginManagerDummy();
    }

    return _loginManager;
}

var LoginManagerSystemd = class {
    constructor() {
        this._proxy = new SystemdLoginManager(Gio.DBus.system,
                                              'org.freedesktop.login1',
                                              '/org/freedesktop/login1');
        this._userProxy = new SystemdLoginUser(Gio.DBus.system,
                                               'org.freedesktop.login1',
                                               '/org/freedesktop/login1/user/self');
        this._proxy.connectSignal('PrepareForSleep',
                                  this._prepareForSleep.bind(this));
    }

    getCurrentSessionProxy(callback) {
        if (this._currentSession) {
            callback (this._currentSession);
            return;
        }

        let sessionId = GLib.getenv('XDG_SESSION_ID');
        if (!sessionId) {
            log('Unset XDG_SESSION_ID, getCurrentSessionProxy() called outside a user session. Asking logind directly.');
            let [session, objectPath] = this._userProxy.Display;
            if (session) {
                log(`Will monitor session ${session}`);
                sessionId = session;
            } else {
                log('Failed to find "Display" session; are we the greeter?');

                for (let [session, objectPath] of this._userProxy.Sessions) {
                    let sessionProxy = new SystemdLoginSession(Gio.DBus.system,
                                                               'org.freedesktop.login1',
                                                               objectPath);
                    log(`Considering ${session}, class=${sessionProxy.Class}`);
                    if (sessionProxy.Class == 'greeter') {
                        log(`Yes, will monitor session ${session}`);
                        sessionId = session;
                        break;
                    }
                }

                if (!sessionId) {
                    log('No, failed to get session from logind.');
                    return;
                }
            }
        }

        this._proxy.GetSessionRemote(sessionId, (result, error) => {
            if (error) {
                logError(error, 'Could not get a proxy for the current session');
            } else {
                this._currentSession = new SystemdLoginSession(Gio.DBus.system,
                                                               'org.freedesktop.login1',
                                                               result[0]);
                callback(this._currentSession);
            }
        });
    }

    canSuspend(asyncCallback) {
        this._proxy.CanSuspendRemote((result, error) => {
            if (error) {
                asyncCallback(false, false);
            } else {
                let needsAuth = result[0] == 'challenge';
                let canSuspend = needsAuth || result[0] == 'yes';
                asyncCallback(canSuspend, needsAuth);
            }
        });
    }

    listSessions(asyncCallback) {
        this._proxy.ListSessionsRemote((result, error) => {
            if (error)
                asyncCallback([]);
            else
                asyncCallback(result[0]);
        });
    }

    suspend() {
        this._proxy.SuspendRemote(true);
    }

    inhibit(reason, callback) {
        let inVariant = GLib.Variant.new('(ssss)',
                                         ['sleep',
                                          'GNOME Shell',
                                          reason,
                                          'delay']);
        this._proxy.call_with_unix_fd_list('Inhibit', inVariant, 0, -1, null, null,
            (proxy, result) => {
                let fd = -1;
                try {
                    let [outVariant, fdList] = proxy.call_with_unix_fd_list_finish(result);
                    fd = fdList.steal_fds()[0];
                    callback(new Gio.UnixInputStream({ fd: fd }));
                } catch(e) {
                    logError(e, "Error getting systemd inhibitor");
                    callback(null);
                }
            });
    }

    _prepareForSleep(proxy, sender, [aboutToSuspend]) {
        this.emit('prepare-for-sleep', aboutToSuspend);
    }
};
Signals.addSignalMethods(LoginManagerSystemd.prototype);

var LoginManagerDummy = class {
    getCurrentSessionProxy(callback) {
        // we could return a DummySession object that fakes whatever callers
        // expect (at the time of writing: connect() and connectSignal()
        // methods), but just never calling the callback should be safer
    }

    canSuspend(asyncCallback) {
        asyncCallback(false, false);
    }

    listSessions(asyncCallback) {
        asyncCallback([]);
    }

    suspend() {
        this.emit('prepare-for-sleep', true);
        this.emit('prepare-for-sleep', false);
    }

    inhibit(reason, callback) {
        callback(null);
    }
};
Signals.addSignalMethods(LoginManagerDummy.prototype);
(uuay)dateMenu.js�T// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, GnomeDesktop,
        GObject, GWeather, Shell, St } = imports.gi;

const Util = imports.misc.util;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const Calendar = imports.ui.calendar;
const Weather = imports.misc.weather;
const System = imports.system;

const MAX_FORECASTS = 5;

function _isToday(date) {
    let now = new Date();
    return now.getYear() == date.getYear() &&
           now.getMonth() == date.getMonth() &&
           now.getDate() == date.getDate();
}

var TodayButton = class TodayButton {
    constructor(calendar) {
        // Having the ability to go to the current date if the user is already
        // on the current date can be confusing. So don't make the button reactive
        // until the selected date changes.
        this.actor = new St.Button({ style_class: 'datemenu-today-button',
                                     x_expand: true, x_align: St.Align.START,
                                     can_focus: true,
                                     reactive: false
                                   });
        this.actor.connect('clicked', () => {
            this._calendar.setDate(new Date(), false);
        });

        let hbox = new St.BoxLayout({ vertical: true });
        this.actor.add_actor(hbox);

        this._dayLabel = new St.Label({ style_class: 'day-label',
                                        x_align: Clutter.ActorAlign.START });
        hbox.add_actor(this._dayLabel);

        this._dateLabel = new St.Label({ style_class: 'date-label' });
        hbox.add_actor(this._dateLabel);

        this._calendar = calendar;
        this._calendar.connect('selected-date-changed', (calendar, date) => {
            // Make the button reactive only if the selected date is not the
            // current date.
            this.actor.reactive = !_isToday(date)
        });
    }

    setDate(date) {
        this._dayLabel.set_text(date.toLocaleFormat('%A'));

        /* Translators: This is the date format to use when the calendar popup is
         * shown - it is shown just below the time in the top bar (e.g.,
         * "Tue 9:29 AM").  The string itself should become a full date, e.g.,
         * "February 17 2015".
         */
        let dateFormat = Shell.util_translate_time_string (N_("%B %-d %Y"));
        this._dateLabel.set_text(date.toLocaleFormat(dateFormat));

        /* Translators: This is the accessible name of the date button shown
         * below the time in the shell; it should combine the weekday and the
         * date, e.g. "Tuesday February 17 2015".
         */
        dateFormat = Shell.util_translate_time_string (N_("%A %B %e %Y"));
        this.actor.accessible_name = date.toLocaleFormat(dateFormat);
    }
};

var WorldClocksSection = class WorldClocksSection {
    constructor() {
        this._clock = new GnomeDesktop.WallClock();
        this._clockNotifyId = 0;

        this._locations = [];

        this.actor = new St.Button({ style_class: 'world-clocks-button',
                                     x_fill: true,
                                     can_focus: true });
        this.actor.connect('clicked', () => {
            this._clockAppMon.activateApp();

            Main.overview.hide();
            Main.panel.closeCalendar();
        });

        let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
        this._grid = new St.Widget({ style_class: 'world-clocks-grid',
                                     layout_manager: layout });
        layout.hookup_style(this._grid);

        this.actor.child = this._grid;

        this._clockAppMon = new Util.AppSettingsMonitor('org.gnome.clocks.desktop',
                                                        'org.gnome.clocks');
        this._clockAppMon.connect('available-changed',
                                  this._sync.bind(this));
        this._clockAppMon.watchSetting('world-clocks',
                                       this._clocksChanged.bind(this));
        this._sync();
    }

    _sync() {
        this.actor.visible = this._clockAppMon.available;
    }

    _clocksChanged(settings) {
        this._grid.destroy_all_children();
        this._locations = [];

        let world = GWeather.Location.get_world();
        let clocks = settings.get_value('world-clocks').deep_unpack();
        for (let i = 0; i < clocks.length; i++) {
            if (!clocks[i].location)
                continue;
            let l = world.deserialize(clocks[i].location);
            if (l && l.get_timezone() != null)
                this._locations.push({ location: l });
        }

        this._locations.sort((a, b) => {
            return a.location.get_timezone().get_offset() -
                   b.location.get_timezone().get_offset();
        });

        let layout = this._grid.layout_manager;
        let title = (this._locations.length == 0) ? _("Add world clocks…")
                                                  : _("World Clocks");
        let header = new St.Label({ style_class: 'world-clocks-header',
                                    x_align: Clutter.ActorAlign.START,
                                    text: title });
        layout.attach(header, 0, 0, 2, 1);
        this.actor.label_actor = header;

        let localOffset = GLib.DateTime.new_now_local().get_utc_offset();

        for (let i = 0; i < this._locations.length; i++) {
            let l = this._locations[i].location;

            let name = l.get_city_name() || l.get_name();
            let label = new St.Label({ style_class: 'world-clocks-city',
                                       text: name,
                                       x_align: Clutter.ActorAlign.START,
                                       y_align: Clutter.ActorAlign.CENTER,
                                       x_expand: true });

            let time = new St.Label({ style_class: 'world-clocks-time' });

            let otherOffset = this._getTimeAtLocation(l).get_utc_offset();
            let offset = (otherOffset - localOffset) / GLib.TIME_SPAN_HOUR;
            let fmt = (Math.trunc(offset) == offset) ? '%s%.0f' : '%s%.1f';
            let prefix = (offset >= 0) ? '+' : '-';
            let tz = new St.Label({ style_class: 'world-clocks-timezone',
                                    text: fmt.format(prefix, Math.abs(offset)),
                                    x_align: Clutter.ActorAlign.END,
                                    y_align: Clutter.ActorAlign.CENTER });

            if (this._grid.text_direction == Clutter.TextDirection.RTL) {
                layout.attach(tz, 0, i + 1, 1, 1);
                layout.attach(time, 1, i + 1, 1, 1);
                layout.attach(label, 2, i + 1, 1, 1);
            } else {
                layout.attach(label, 0, i + 1, 1, 1);
                layout.attach(time, 1, i + 1, 1, 1);
                layout.attach(tz, 2, i + 1, 1, 1);
            }

            this._locations[i].actor = time;
        }

        if (this._grid.get_n_children() > 1) {
            if (!this._clockNotifyId)
                this._clockNotifyId =
                    this._clock.connect('notify::clock', this._updateLabels.bind(this));
            this._updateLabels();
        } else {
            if (this._clockNotifyId)
                this._clock.disconnect(this._clockNotifyId);
            this._clockNotifyId = 0;
        }
    }

    _getTimeAtLocation(location) {
        let tz = GLib.TimeZone.new(location.get_timezone().get_tzid());
        return GLib.DateTime.new_now(tz);
    }

    _updateLabels() {
        for (let i = 0; i < this._locations.length; i++) {
            let l = this._locations[i];
            let now = this._getTimeAtLocation(l.location);
            l.actor.text = Util.formatTime(now, { timeOnly: true });
        }
    }
};

var WeatherSection = class WeatherSection {
    constructor() {
        this._weatherClient = new Weather.WeatherClient();

        this.actor = new St.Button({ style_class: 'weather-button',
                                     x_fill: true,
                                     can_focus: true });
        this.actor.connect('clicked', () => {
            this._weatherClient.activateApp();

            Main.overview.hide();
            Main.panel.closeCalendar();
        });
        this.actor.connect('notify::mapped', () => {
            if (this.actor.mapped)
                this._weatherClient.update();
        });

        let box = new St.BoxLayout({ style_class: 'weather-box',
                                      vertical: true });

        this.actor.child = box;

        let titleBox = new St.BoxLayout();
        titleBox.add_child(new St.Label({ style_class: 'weather-header',
                                          x_align: Clutter.ActorAlign.START,
                                          x_expand: true,
                                          text: _("Weather") }));
        box.add_child(titleBox);

        this._titleLocation = new St.Label({ style_class: 'weather-header location',
                                             x_align: Clutter.ActorAlign.END });
        titleBox.add_child(this._titleLocation);

        let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
        this._forecastGrid = new St.Widget({ style_class: 'weather-grid',
                                             layout_manager: layout });
        layout.hookup_style(this._forecastGrid);
        box.add_child(this._forecastGrid);

        this._weatherClient.connect('changed', this._sync.bind(this));
        this._sync();
    }

    _getInfos() {
        let info = this._weatherClient.info;
        let forecasts = info.get_forecast_list();

        let current = info;
        let infos = [info];
        for (let i = 0; i < forecasts.length; i++) {
            let [ok, timestamp] = forecasts[i].get_value_update();
            let datetime = new Date(timestamp * 1000);
            if (!_isToday(datetime))
                continue; // Ignore forecasts from other days

            [ok, timestamp] = current.get_value_update();
            let currenttime = new Date(timestamp * 1000);
            if (currenttime.getHours() == datetime.getHours())
                continue; // Enforce a minimum interval of 1h

            current = forecasts[i];
            if (infos.push(current) == MAX_FORECASTS)
                break; // Use a maximum of five forecasts
        }
        return infos;
    }

    _addForecasts() {
        let layout = this._forecastGrid.layout_manager;

        let infos = this._getInfos();
        if (this._forecastGrid.text_direction == Clutter.TextDirection.RTL)
            infos.reverse();

        let col = 0;
        infos.forEach(fc => {
            let [ok, timestamp] = fc.get_value_update();
            let timeStr = Util.formatTime(new Date(timestamp * 1000), {
                timeOnly: true
            });

            let icon = new St.Icon({ style_class: 'weather-forecast-icon',
                                     icon_name: fc.get_symbolic_icon_name(),
                                     x_align: Clutter.ActorAlign.CENTER,
                                     x_expand: true });
            let temp = new St.Label({ style_class: 'weather-forecast-temp',
                                      text: fc.get_temp_summary(),
                                      x_align: Clutter.ActorAlign.CENTER });
            let time = new St.Label({ style_class: 'weather-forecast-time',
                                      text: timeStr,
                                      x_align: Clutter.ActorAlign.CENTER });

            layout.attach(icon, col, 0, 1, 1);
            layout.attach(temp, col, 1, 1, 1);
            layout.attach(time, col, 2, 1, 1);
            col++;
        });
    }

    _setStatusLabel(text) {
        let layout = this._forecastGrid.layout_manager;
        let label = new St.Label({ text });
        layout.attach(label, 0, 0, 1, 1);
    }

    _updateForecasts() {
        this._forecastGrid.destroy_all_children();

        if (!this._weatherClient.hasLocation) {
            this._setStatusLabel(_("Select a location…"));
            return;
        }

        let info = this._weatherClient.info;
        this._titleLocation.text = info.get_location().get_name();

        if (this._weatherClient.loading) {
            this._setStatusLabel(_("Loading…"));
            return;
        }

        if (info.is_valid()) {
            this._addForecasts();
            return;
        }

        if (info.network_error())
            this._setStatusLabel(_("Go online for weather information"));
        else
            this._setStatusLabel(_("Weather information is currently unavailable"));
    }

    _sync() {
        this.actor.visible = this._weatherClient.available;

        if (!this.actor.visible)
            return;

        this._titleLocation.visible = this._weatherClient.hasLocation;

        this._updateForecasts();
    }
};

var MessagesIndicator = class MessagesIndicator {
    constructor() {
        this.actor = new St.Icon({ icon_name: 'message-indicator-symbolic',
                                   icon_size: 16,
                                   visible: false, y_expand: true,
                                   y_align: Clutter.ActorAlign.CENTER });

        this._sources = [];

        Main.messageTray.connect('source-added', this._onSourceAdded.bind(this));
        Main.messageTray.connect('source-removed', this._onSourceRemoved.bind(this));
        Main.messageTray.connect('queue-changed', this._updateCount.bind(this));

        let sources = Main.messageTray.getSources();
        sources.forEach(source => { this._onSourceAdded(null, source); });
    }

    _onSourceAdded(tray, source) {
        source.connect('count-updated', this._updateCount.bind(this));
        this._sources.push(source);
        this._updateCount();
    }

    _onSourceRemoved(tray, source) {
        this._sources.splice(this._sources.indexOf(source), 1);
        this._updateCount();
    }

    _updateCount() {
        let count = 0;
        this._sources.forEach(source => { count += source.unseenCount; });
        count -= Main.messageTray.queueCount;

        this.actor.visible = (count > 0);
    }
};

var IndicatorPad = GObject.registerClass(
class IndicatorPad extends St.Widget {
    _init(actor) {
        this._source = actor;
        this._source.connect('notify::visible', () => { this.queue_relayout(); });
        this._source.connect('notify::size', () => { this.queue_relayout(); });
        super._init();
    }

    vfunc_get_preferred_width(forHeight) {
        if (this._source.visible)
            return this._source.get_preferred_width(forHeight);
        return [0, 0];
    }

    vfunc_get_preferred_height(forWidth) {
        if (this._source.visible)
            return this._source.get_preferred_height(forWidth);
        return [0, 0];
    }
});

var FreezableBinLayout = GObject.registerClass(
class FreezableBinLayout extends Clutter.BinLayout {
    _init() {
        super._init();

        this._frozen = false;
        this._savedWidth = [NaN, NaN];
        this._savedHeight = [NaN, NaN];
    }

    set frozen(v) {
        if (this._frozen == v)
            return;

        this._frozen = v;
        if (!this._frozen)
            this.layout_changed();
    }

    vfunc_get_preferred_width(container, forHeight) {
        if (!this._frozen || this._savedWidth.some(isNaN))
            return super.vfunc_get_preferred_width(container, forHeight);
        return this._savedWidth;
    }

    vfunc_get_preferred_height(container, forWidth) {
        if (!this._frozen || this._savedHeight.some(isNaN))
            return super.vfunc_get_preferred_height(container, forWidth);
        return this._savedHeight;
    }

    vfunc_allocate(container, allocation, flags) {
        super.vfunc_allocate(container, allocation, flags);

        let [width, height] = allocation.get_size();
        this._savedWidth = [width, width];
        this._savedHeight = [height, height];
    }
});

var CalendarColumnLayout = GObject.registerClass(
class CalendarColumnLayout extends Clutter.BoxLayout {
    _init(actor) {
        super._init({ orientation: Clutter.Orientation.VERTICAL });
        this._calActor = actor;
    }

    vfunc_get_preferred_width(container, forHeight) {
        if (!this._calActor || this._calActor.get_parent() != container)
            return super.vfunc_get_preferred_width(container, forHeight);
        return this._calActor.get_preferred_width(forHeight);
    }
});

var DateMenuButton = GObject.registerClass(
class DateMenuButton extends PanelMenu.Button {
    _init() {
        let item;
        let hbox;
        let vbox;

        let menuAlignment = 0.5;
        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
            menuAlignment = 1.0 - menuAlignment;
        super._init(menuAlignment);

        this._clockDisplay = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
        this._indicator = new MessagesIndicator();

        let box = new St.BoxLayout();
        box.add_actor(new IndicatorPad(this._indicator.actor));
        box.add_actor(this._clockDisplay);
        box.add_actor(this._indicator.actor);

        this.actor.label_actor = this._clockDisplay;
        this.actor.add_actor(box);
        this.actor.add_style_class_name ('clock-display');


        let layout = new FreezableBinLayout();
        let bin = new St.Widget({ layout_manager: layout });
        // For some minimal compatibility with PopupMenuItem
        bin._delegate = this;
        this.menu.box.add_child(bin);

        hbox = new St.BoxLayout({ name: 'calendarArea' });
        bin.add_actor(hbox);

        this._calendar = new Calendar.Calendar();
        this._calendar.connect('selected-date-changed',
                               (calendar, date) => {
                                   layout.frozen = !_isToday(date);
                                   this._messageList.setDate(date);
                               });

        this.menu.connect('open-state-changed', (menu, isOpen) => {
            // Whenever the menu is opened, select today
            if (isOpen) {
                let now = new Date();
                this._calendar.setDate(now);
                this._date.setDate(now);
                this._messageList.setDate(now);
            }
        });

        // Fill up the first column
        this._messageList = new Calendar.CalendarMessageList();
        hbox.add(this._messageList.actor, { expand: true, y_fill: false, y_align: St.Align.START });

        // Fill up the second column
        let boxLayout = new CalendarColumnLayout(this._calendar.actor);
        vbox = new St.Widget({ style_class: 'datemenu-calendar-column',
                               layout_manager: boxLayout });
        boxLayout.hookup_style(vbox);
        hbox.add(vbox);

        this._date = new TodayButton(this._calendar);
        vbox.add_actor(this._date.actor);

        vbox.add_actor(this._calendar.actor);

        this._displaysSection = new St.ScrollView({ style_class: 'datemenu-displays-section vfade',
                                                    x_expand: true, x_fill: true,
                                                    overlay_scrollbars: true });
        this._displaysSection.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        vbox.add_actor(this._displaysSection);

        let displaysBox = new St.BoxLayout({ vertical: true,
                                             style_class: 'datemenu-displays-box' });
        this._displaysSection.add_actor(displaysBox);

        this._clocksItem = new WorldClocksSection();
        displaysBox.add(this._clocksItem.actor, { x_fill: true });

        this._weatherItem = new WeatherSection();
        displaysBox.add(this._weatherItem.actor, { x_fill: true });

        // Done with hbox for calendar and event list

        this._clock = new GnomeDesktop.WallClock();
        this._clock.bind_property('clock', this._clockDisplay, 'text', GObject.BindingFlags.SYNC_CREATE);
        this._clock.connect('notify::timezone', this._updateTimeZone.bind(this));

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    _getEventSource() {
        return new Calendar.DBusEventSource();
    }

    _setEventSource(eventSource) {
        if (this._eventSource)
            this._eventSource.destroy();

        this._calendar.setEventSource(eventSource);
        this._messageList.setEventSource(eventSource);

        this._eventSource = eventSource;
    }

    _updateTimeZone() {
        // SpiderMonkey caches the time zone so we must explicitly clear it
        // before we can update the calendar, see
        // https://bugzilla.gnome.org/show_bug.cgi?id=678507
        System.clearDateCaches();

        this._calendar.updateTimeZone();
    }

    _sessionUpdated() {
        let eventSource;
        let showEvents = Main.sessionMode.showCalendarEvents;
        if (showEvents) {
            eventSource = this._getEventSource();
        } else {
            eventSource = new Calendar.EmptyEventSource();
        }
        this._setEventSource(eventSource);

        // Displays are not actually expected to launch Settings when activated
        // but the corresponding app (clocks, weather); however we can consider
        // that display-specific settings, so re-use "allowSettings" here ...
        this._displaysSection.visible = Main.sessionMode.allowSettings;
    }
});
(uuay)accessDialog.jsiconst { Clutter, Gio, GLib, Shell } = imports.gi;

const CheckBox = imports.ui.checkBox;
const Dialog = imports.ui.dialog;
const ModalDialog = imports.ui.modalDialog;

const { loadInterfaceXML } = imports.misc.fileUtils;

const RequestIface = loadInterfaceXML('org.freedesktop.impl.portal.Request');
const AccessIface = loadInterfaceXML('org.freedesktop.impl.portal.Access');

var DialogResponse = {
    OK: 0,
    CANCEL: 1,
    CLOSED: 2
};

var AccessDialog = class extends ModalDialog.ModalDialog {
    constructor(invocation, handle, title, subtitle, body, options) {
        super({ styleClass: 'access-dialog' });

        this._invocation = invocation;
        this._handle = handle;

        this._requestExported = false;
        this._request = Gio.DBusExportedObject.wrapJSObject(RequestIface, this);

        for (let option in options)
            options[option] = options[option].deep_unpack();

        this._buildLayout(title, subtitle, body, options);
    }

    _buildLayout(title, subtitle, body, options) {
        // No support for non-modal system dialogs, so ignore the option
        //let modal = options['modal'] || true;
        let denyLabel = options['deny_label'] || _("Deny Access");
        let grantLabel = options['grant_label'] || _("Grant Access");
        let iconName = options['icon'] || null;
        let choices = options['choices'] || [];

        let contentParams = { title, subtitle, body };
        if (iconName)
            contentParams.icon = new Gio.ThemedIcon({ name: iconName });
        let content = new Dialog.MessageDialogContent(contentParams);
        this.contentLayout.add_actor(content);

        this._choices = new Map();

        for (let i = 0; i < choices.length; i++) {
            let [id, name, opts, selected] = choices[i];
            if (opts.length > 0)
                continue; // radio buttons, not implemented

            let check = new CheckBox.CheckBox();
            check.getLabelActor().text = name;
            check.actor.checked = selected == "true";
            content.insertBeforeBody(check.actor);

            this._choices.set(id, check);
        }

        this.addButton({ label: denyLabel,
                         action: () => {
                             this._sendResponse(DialogResponse.CANCEL);
                         },
                         key: Clutter.KEY_Escape });
        this.addButton({ label: grantLabel,
                         action: () => {
                             this._sendResponse(DialogResponse.OK);
                         }});
    }

    open() {
        super.open();

        let connection = this._invocation.get_connection();
        this._requestExported = this._request.export(connection, this._handle);
    }

    CloseAsync(invocation, params) {
        if (this._invocation.get_sender() != invocation.get_sender()) {
            invocation.return_error_literal(Gio.DBusError,
                                            Gio.DBusError.ACCESS_DENIED,
                                            '');
            return;
        }

        this._sendResponse(DialogResponse.CLOSED);
    }

    _sendResponse(response) {
        if (this._requestExported)
            this._request.unexport();
        this._requestExported = false;

        let results = {};
        if (response == DialogResponse.OK) {
            for (let [id, check] of this._choices) {
                let checked = check.actor.checked ? 'true' : 'false';
                results[id] = new GLib.Variant('s', checked);
            }
        }

        // Delay actual response until the end of the close animation (if any)
        this.connect('closed', () => {
            this._invocation.return_value(new GLib.Variant('(ua{sv})',
                                                           [response, results]));
        });
        this.close();
    }
};

var AccessDialogDBus = class {
    constructor() {
        this._accessDialog = null;

        this._windowTracker = Shell.WindowTracker.get_default();

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(AccessIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/portal/desktop');

        Gio.DBus.session.own_name('org.freedesktop.impl.portal.desktop.gnome', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    AccessDialogAsync(params, invocation) {
        if (this._accessDialog) {
            invocation.return_error_literal(Gio.DBusError,
                                            Gio.DBusError.LIMITS_EXCEEDED,
                                            'Already showing a system access dialog');
            return;
        }

        let [handle, appId, parentWindow, title, subtitle, body, options] = params;
        // We probably want to use parentWindow and global.display.focus_window
        // for this check in the future
        if (appId && appId + '.desktop' != this._windowTracker.focus_app.id) {
            invocation.return_error_literal(Gio.DBusError,
                                            Gio.DBusError.ACCESS_DENIED,
                                            'Only the focused app is allowed to show a system access dialog');
            return;
        }

        let dialog = new AccessDialog(invocation, handle, title,
                                      subtitle, body, options);
        dialog.open();

        dialog.connect('closed', () => { this._accessDialog = null; });

        this._accessDialog = dialog;
    }
};
(uuay)main.js�g// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;

const AccessDialog = imports.ui.accessDialog;
const AudioDeviceSelection = imports.ui.audioDeviceSelection;
const Components = imports.ui.components;
const CtrlAltTab = imports.ui.ctrlAltTab;
const EndSessionDialog = imports.ui.endSessionDialog;
const ExtensionSystem = imports.ui.extensionSystem;
const ExtensionDownloader = imports.ui.extensionDownloader;
const InputMethod = imports.misc.inputMethod;
const Introspect = imports.misc.introspect;
const Keyboard = imports.ui.keyboard;
const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog;
const OsdWindow = imports.ui.osdWindow;
const OsdMonitorLabeler = imports.ui.osdMonitorLabeler;
const Overview = imports.ui.overview;
const PadOsd = imports.ui.padOsd;
const Panel = imports.ui.panel;
const Params = imports.misc.params;
const RunDialog = imports.ui.runDialog;
const Layout = imports.ui.layout;
const LoginManager = imports.misc.loginManager;
const LookingGlass = imports.ui.lookingGlass;
const NotificationDaemon = imports.ui.notificationDaemon;
const WindowAttentionHandler = imports.ui.windowAttentionHandler;
const Screencast = imports.ui.screencast;
const ScreenShield = imports.ui.screenShield;
const Scripting = imports.ui.scripting;
const SessionMode = imports.ui.sessionMode;
const ShellDBus = imports.ui.shellDBus;
const ShellMountOperation = imports.ui.shellMountOperation;
const WindowManager = imports.ui.windowManager;
const Magnifier = imports.ui.magnifier;
const XdndHandler = imports.ui.xdndHandler;
const KbdA11yDialog = imports.ui.kbdA11yDialog;

const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';
const STICKY_KEYS_ENABLE = 'stickykeys-enable';
const GNOMESHELL_STARTED_MESSAGE_ID = 'f3ea493c22934e26811cd62abe8e203a';

var componentManager = null;
var extensionManager = null;
var panel = null;
var overview = null;
var runDialog = null;
var lookingGlass = null;
var wm = null;
var messageTray = null;
var screenShield = null;
var notificationDaemon = null;
var windowAttentionHandler = null;
var ctrlAltTabManager = null;
var padOsdService = null;
var osdWindowManager = null;
var osdMonitorLabeler = null;
var sessionMode = null;
var shellAccessDialogDBusService = null;
var shellAudioSelectionDBusService = null;
var shellDBusService = null;
var shellMountOpDBusService = null;
var screenSaverDBus = null;
var screencastService = null;
var modalCount = 0;
var actionMode = Shell.ActionMode.NONE;
var modalActorFocusStack = [];
var uiGroup = null;
var magnifier = null;
var xdndHandler = null;
var keyboard = null;
var layoutManager = null;
var kbdA11yDialog = null;
var inputMethod = null;
var introspectService = null;
let _startDate;
let _defaultCssStylesheet = null;
let _cssStylesheet = null;
let _a11ySettings = null;
let _themeResource = null;
let _oskResource = null;

Gio._promisify(Gio._LocalFilePrototype, 'delete_async', 'delete_finish');
Gio._promisify(Gio._LocalFilePrototype, 'touch_async', 'touch_finish');

function _sessionUpdated() {
    if (sessionMode.isPrimary)
        _loadDefaultStylesheet();

    wm.setCustomKeybindingHandler('panel-main-menu',
                                  Shell.ActionMode.NORMAL |
                                  Shell.ActionMode.OVERVIEW,
                                  sessionMode.hasOverview ? overview.toggle.bind(overview) : null);
    wm.allowKeybinding('overlay-key', Shell.ActionMode.NORMAL |
                                      Shell.ActionMode.OVERVIEW);

    wm.setCustomKeybindingHandler('panel-run-dialog',
                                  Shell.ActionMode.NORMAL |
                                  Shell.ActionMode.OVERVIEW,
                                  sessionMode.hasRunDialog ? openRunDialog : null);

    if (!sessionMode.hasRunDialog) {
        if (runDialog)
            runDialog.close();
        if (lookingGlass)
            lookingGlass.close();
    }
}

function start() {
    // These are here so we don't break compatibility.
    global.logError = window.log;
    global.log = window.log;

    // Chain up async errors reported from C
    global.connect('notify-error', (global, msg, detail) => {
        notifyError(msg, detail);
    });

    Gio.DesktopAppInfo.set_desktop_env('GNOME');

    sessionMode = new SessionMode.SessionMode();
    sessionMode.connect('updated', _sessionUpdated);

    St.Settings.get().connect('notify::gtk-theme', _loadDefaultStylesheet);
    _initializeUI();

    shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus();
    shellAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus();
    shellDBusService = new ShellDBus.GnomeShell();
    shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();

    _sessionUpdated();
}

function _initializeUI() {
    // Ensure ShellWindowTracker and ShellAppUsage are initialized; this will
    // also initialize ShellAppSystem first.  ShellAppSystem
    // needs to load all the .desktop files, and ShellWindowTracker
    // will use those to associate with windows.  Right now
    // the Monitor doesn't listen for installed app changes
    // and recalculate application associations, so to avoid
    // races for now we initialize it here.  It's better to
    // be predictable anyways.
    Shell.WindowTracker.get_default();
    Shell.AppUsage.get_default();

    reloadThemeResource();
    _loadOskLayouts();
    _loadDefaultStylesheet();

    new AnimationsSettings();

    // Setup the stage hierarchy early
    layoutManager = new Layout.LayoutManager();

    // Various parts of the codebase still refers to Main.uiGroup
    // instead using the layoutManager.  This keeps that code
    // working until it's updated.
    uiGroup = layoutManager.uiGroup;

    padOsdService = new PadOsd.PadOsdService();
    screencastService = new Screencast.ScreencastService();
    xdndHandler = new XdndHandler.XdndHandler();
    ctrlAltTabManager = new CtrlAltTab.CtrlAltTabManager();
    osdWindowManager = new OsdWindow.OsdWindowManager();
    osdMonitorLabeler = new OsdMonitorLabeler.OsdMonitorLabeler();
    overview = new Overview.Overview();
    kbdA11yDialog = new KbdA11yDialog.KbdA11yDialog();
    wm = new WindowManager.WindowManager();
    magnifier = new Magnifier.Magnifier();
    if (LoginManager.canLock())
        screenShield = new ScreenShield.ScreenShield();

    inputMethod = new InputMethod.InputMethod();
    Clutter.get_default_backend().set_input_method(inputMethod);

    messageTray = new MessageTray.MessageTray();
    panel = new Panel.Panel();
    keyboard = new Keyboard.Keyboard();
    notificationDaemon = new NotificationDaemon.NotificationDaemon();
    windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
    componentManager = new Components.ComponentManager();

    introspectService = new Introspect.IntrospectService();

    layoutManager.init();
    overview.init();

    _a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });

    global.display.connect('overlay-key', () => {
        if (!_a11ySettings.get_boolean (STICKY_KEYS_ENABLE))
            overview.toggle();
    });

    global.display.connect('show-restart-message', (display, message) => {
        showRestartMessage(message);
        return true;
    });

    global.display.connect('restart', () => {
        global.reexec_self();
        return true;
    });

    global.display.connect('gl-video-memory-purged', () => {
        let cache = St.TextureCache.get_default();
        cache.clear();
        loadTheme();
    });

    // Provide the bus object for gnome-session to
    // initiate logouts.
    EndSessionDialog.init();

    // We're ready for the session manager to move to the next phase
    Meta.register_with_session();

    _startDate = new Date();

    ExtensionDownloader.init();
    extensionManager = new ExtensionSystem.ExtensionManager();
    extensionManager.init();

    if (sessionMode.isGreeter && screenShield) {
        layoutManager.connect('startup-prepared', () => {
            screenShield.showDialog();
        });
    }

    layoutManager.connect('startup-complete', () => {
        if (actionMode == Shell.ActionMode.NONE) {
            actionMode = Shell.ActionMode.NORMAL;
        }
        if (screenShield) {
            screenShield.lockIfWasLocked();
        }
        if (sessionMode.currentMode != 'gdm' &&
            sessionMode.currentMode != 'initial-setup') {
            Shell.Global.log_structured('GNOME Shell started at ' + _startDate,
                                        ['MESSAGE_ID=' + GNOMESHELL_STARTED_MESSAGE_ID]);
        }

        let credentials = new Gio.Credentials();
        if (credentials.get_unix_user() === 0) {
            notify(_('Logged in as a privileged user'),
                   _('Running a session as a privileged user should be avoided for security reasons. If possible, you should log in as a normal user.'));
        }

        if (sessionMode.currentMode !== 'gdm' &&
            sessionMode.currentMode !== 'initial-setup')
            _handleLockScreenWarning();

        let perfModuleName = GLib.getenv("SHELL_PERF_MODULE");
        if (perfModuleName) {
            let perfOutput = GLib.getenv("SHELL_PERF_OUTPUT");
            let module = eval('imports.perf.' + perfModuleName + ';');
            Scripting.runPerfScript(module, perfOutput);
        }
    });
}

async function _handleLockScreenWarning() {
    const path = '%s/lock-warning-shown'.format(global.userdatadir);
    const file = Gio.File.new_for_path(path);

    const hasLockScreen = screenShield !== null;
    if (hasLockScreen) {
        try {
            await file.delete_async(0, null);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
                logError(e);
        }
    } else {
        try {
            if (!await file.touch_async())
                return;
        } catch (e) {
            logError(e);
        }

        notify(
            _('Screen Lock disabled'),
            _('Screen Locking requires the GNOME display manager.'));
    }
}

function _getStylesheet(name) {
    let stylesheet;

    stylesheet = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/' + name);
    if (stylesheet.query_exists(null))
        return stylesheet;

    let dataDirs = GLib.get_system_data_dirs();
    for (let i = 0; i < dataDirs.length; i++) {
        let path = GLib.build_filenamev([dataDirs[i], 'gnome-shell', 'theme', name]);
        let stylesheet = Gio.file_new_for_path(path);
        if (stylesheet.query_exists(null))
            return stylesheet;
    }

    stylesheet = Gio.File.new_for_path(global.datadir + '/theme/' + name);
    if (stylesheet.query_exists(null))
        return stylesheet;

    return null;
}

function _getDefaultStylesheet() {
    let stylesheet = null;
    let name = sessionMode.stylesheetName;

    // Look for a high-contrast variant first when using GTK+'s HighContrast
    // theme
    if (St.Settings.get().gtk_theme == 'HighContrast')
        stylesheet = _getStylesheet(name.replace('.css', '-high-contrast.css'));

    if (stylesheet == null)
        stylesheet = _getStylesheet(sessionMode.stylesheetName);

    return stylesheet;
}

function _loadDefaultStylesheet() {
    let stylesheet = _getDefaultStylesheet();
    if (_defaultCssStylesheet && _defaultCssStylesheet.equal(stylesheet))
        return;

    _defaultCssStylesheet = stylesheet;
    loadTheme();
}

/**
 * getThemeStylesheet:
 *
 * Get the theme CSS file that the shell will load
 *
 * Returns: A #GFile that contains the theme CSS,
 *          null if using the default
 */
function getThemeStylesheet() {
    return _cssStylesheet;
}

/**
 * setThemeStylesheet:
 * @cssStylesheet: A file path that contains the theme CSS,
 *                  set it to null to use the default
 *
 * Set the theme CSS file that the shell will load
 */
function setThemeStylesheet(cssStylesheet) {
    _cssStylesheet = cssStylesheet ? Gio.File.new_for_path(cssStylesheet) : null;
}

function reloadThemeResource() {
    if (_themeResource)
        _themeResource._unregister();

    _themeResource = Gio.Resource.load(global.datadir + '/gnome-shell-theme.gresource');
    _themeResource._register();
}

function _loadOskLayouts() {
    _oskResource = Gio.Resource.load(global.datadir + '/gnome-shell-osk-layouts.gresource');
    _oskResource._register();
}

/**
 * loadTheme:
 *
 * Reloads the theme CSS file
 */
function loadTheme() {
    let themeContext = St.ThemeContext.get_for_stage (global.stage);
    let previousTheme = themeContext.get_theme();

    let theme = new St.Theme ({ application_stylesheet: _cssStylesheet,
                                default_stylesheet: _defaultCssStylesheet });

    if (theme.default_stylesheet == null)
        throw new Error("No valid stylesheet found for '%s'".format(sessionMode.stylesheetName));

    if (previousTheme) {
        let customStylesheets = previousTheme.get_custom_stylesheets();

        for (let i = 0; i < customStylesheets.length; i++)
            theme.load_stylesheet(customStylesheets[i]);
    }

    themeContext.set_theme (theme);
}

/**
 * notify:
 * @msg: A message
 * @details: Additional information
 */
function notify(msg, details) {
    let source = new MessageTray.SystemNotificationSource();
    messageTray.add(source);
    let notification = new MessageTray.Notification(source, msg, details);
    notification.setTransient(true);
    source.notify(notification);
}

/**
 * notifyError:
 * @msg: An error message
 * @details: Additional information
 *
 * See shell_global_notify_problem().
 */
function notifyError(msg, details) {
    // Also print to stderr so it's logged somewhere
    if (details)
        log('error: ' + msg + ': ' + details);
    else
        log('error: ' + msg);

    notify(msg, details);
}

function _findModal(actor) {
    for (let i = 0; i < modalActorFocusStack.length; i++) {
        if (modalActorFocusStack[i].actor == actor)
            return i;
    }
    return -1;
}

/**
 * pushModal:
 * @actor: #ClutterActor which will be given keyboard focus
 * @params: optional parameters
 *
 * Ensure we are in a mode where all keyboard and mouse input goes to
 * the stage, and focus @actor. Multiple calls to this function act in
 * a stacking fashion; the effect will be undone when an equal number
 * of popModal() invocations have been made.
 *
 * Next, record the current Clutter keyboard focus on a stack. If the
 * modal stack returns to this actor, reset the focus to the actor
 * which was focused at the time pushModal() was invoked.
 *
 * @params may be used to provide the following parameters:
 *  - timestamp: used to associate the call with a specific user initiated
 *               event.  If not provided then the value of
 *               global.get_current_time() is assumed.
 *
 *  - options: Meta.ModalOptions flags to indicate that the pointer is
 *             already grabbed
 *
 *  - actionMode: used to set the current Shell.ActionMode to filter
 *                    global keybindings; the default of NONE will filter
 *                    out all keybindings
 *
 * Returns: true iff we successfully acquired a grab or already had one
 */
function pushModal(actor, params) {
    params = Params.parse(params, { timestamp: global.get_current_time(),
                                    options: 0,
                                    actionMode: Shell.ActionMode.NONE });

    if (modalCount == 0) {
        if (!global.begin_modal(params.timestamp, params.options)) {
            log('pushModal: invocation of begin_modal failed');
            return false;
        }
        Meta.disable_unredirect_for_display(global.display);
    }

    modalCount += 1;
    let actorDestroyId = actor.connect('destroy', () => {
        let index = _findModal(actor);
        if (index >= 0)
            popModal(actor);
    });

    let prevFocus = global.stage.get_key_focus();
    let prevFocusDestroyId;
    if (prevFocus != null) {
        prevFocusDestroyId = prevFocus.connect('destroy', () => {
            const index = modalActorFocusStack.findIndex(
                record => record.prevFocus === prevFocus);

            if (index >= 0)
                modalActorFocusStack[index].prevFocus = null;
        });
    }
    modalActorFocusStack.push({ actor: actor,
                                destroyId: actorDestroyId,
                                prevFocus: prevFocus,
                                prevFocusDestroyId: prevFocusDestroyId,
                                actionMode: actionMode });

    actionMode = params.actionMode;
    global.stage.set_key_focus(actor);
    return true;
}

/**
 * popModal:
 * @actor: #ClutterActor passed to original invocation of pushModal().
 * @timestamp: optional timestamp
 *
 * Reverse the effect of pushModal().  If this invocation is undoing
 * the topmost invocation, then the focus will be restored to the
 * previous focus at the time when pushModal() was invoked.
 *
 * @timestamp is optionally used to associate the call with a specific user
 * initiated event.  If not provided then the value of
 * global.get_current_time() is assumed.
 */
function popModal(actor, timestamp) {
    if (timestamp == undefined)
        timestamp = global.get_current_time();

    let focusIndex = _findModal(actor);
    if (focusIndex < 0) {
        global.stage.set_key_focus(null);
        global.end_modal(timestamp);
        actionMode = Shell.ActionMode.NORMAL;

        throw new Error('incorrect pop');
    }

    modalCount -= 1;

    let record = modalActorFocusStack[focusIndex];
    record.actor.disconnect(record.destroyId);

    if (focusIndex == modalActorFocusStack.length - 1) {
        if (record.prevFocus)
            record.prevFocus.disconnect(record.prevFocusDestroyId);
        actionMode = record.actionMode;
        global.stage.set_key_focus(record.prevFocus);
    } else {
        // If we have:
        //     global.stage.set_focus(a);
        //     Main.pushModal(b);
        //     Main.pushModal(c);
        //     Main.pushModal(d);
        //
        // then we have the stack:
        //     [{ prevFocus: a, actor: b },
        //      { prevFocus: b, actor: c },
        //      { prevFocus: c, actor: d }]
        //
        // When actor c is destroyed/popped, if we only simply remove the
        // record, then the focus stack will be [a, c], rather than the correct
        // [a, b]. Shift the focus stack up before removing the record to ensure
        // that we get the correct result.
        let t = modalActorFocusStack[modalActorFocusStack.length - 1];
        if (t.prevFocus)
            t.prevFocus.disconnect(t.prevFocusDestroyId);
        // Remove from the middle, shift the focus chain up
        for (let i = modalActorFocusStack.length - 1; i > focusIndex; i--) {
            modalActorFocusStack[i].prevFocus = modalActorFocusStack[i - 1].prevFocus;
            modalActorFocusStack[i].prevFocusDestroyId = modalActorFocusStack[i - 1].prevFocusDestroyId;
            modalActorFocusStack[i].actionMode = modalActorFocusStack[i - 1].actionMode;
        }
    }
    modalActorFocusStack.splice(focusIndex, 1);

    if (modalCount > 0)
        return;

    layoutManager.modalEnded();
    global.end_modal(timestamp);
    Meta.enable_unredirect_for_display(global.display);
    actionMode = Shell.ActionMode.NORMAL;
}

function createLookingGlass() {
    if (lookingGlass == null) {
        lookingGlass = new LookingGlass.LookingGlass();
    }
    return lookingGlass;
}

function openRunDialog() {
    if (runDialog == null) {
        runDialog = new RunDialog.RunDialog();
    }
    runDialog.open();
}

/**
 * activateWindow:
 * @window: the Meta.Window to activate
 * @time: (optional) current event time
 * @workspaceNum: (optional) window's workspace number
 *
 * Activates @window, switching to its workspace first if necessary,
 * and switching out of the overview if it's currently active
 */
function activateWindow(window, time, workspaceNum) {
    let workspaceManager = global.workspace_manager;
    let activeWorkspaceNum = workspaceManager.get_active_workspace_index();
    let windowWorkspaceNum = (workspaceNum !== undefined) ? workspaceNum : window.get_workspace().index();

    if (!time)
        time = global.get_current_time();

    if (windowWorkspaceNum != activeWorkspaceNum) {
        let workspace = workspaceManager.get_workspace_by_index(windowWorkspaceNum);
        workspace.activate_with_focus(window, time);
    } else {
        window.activate(time);
    }

    overview.hide();
    panel.closeCalendar();
}

// TODO - replace this timeout with some system to guess when the user might
// be e.g. just reading the screen and not likely to interact.
var DEFERRED_TIMEOUT_SECONDS = 20;
var _deferredWorkData = {};
// Work scheduled for some point in the future
var _deferredWorkQueue = [];
// Work we need to process before the next redraw
var _beforeRedrawQueue = [];
// Counter to assign work ids
var _deferredWorkSequence = 0;
var _deferredTimeoutId = 0;

function _runDeferredWork(workId) {
    if (!_deferredWorkData[workId])
        return;
    let index = _deferredWorkQueue.indexOf(workId);
    if (index < 0)
        return;

    _deferredWorkQueue.splice(index, 1);
    _deferredWorkData[workId].callback();
    if (_deferredWorkQueue.length == 0 && _deferredTimeoutId > 0) {
        Mainloop.source_remove(_deferredTimeoutId);
        _deferredTimeoutId = 0;
    }
}

function _runAllDeferredWork() {
    while (_deferredWorkQueue.length > 0)
        _runDeferredWork(_deferredWorkQueue[0]);
}

function _runBeforeRedrawQueue() {
    for (let i = 0; i < _beforeRedrawQueue.length; i++) {
        let workId = _beforeRedrawQueue[i];
        _runDeferredWork(workId);
    }
    _beforeRedrawQueue = [];
}

function _queueBeforeRedraw(workId) {
    _beforeRedrawQueue.push(workId);
    if (_beforeRedrawQueue.length == 1) {
        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            _runBeforeRedrawQueue();
            return false;
        });
    }
}

/**
 * initializeDeferredWork:
 * @actor: A #ClutterActor
 * @callback: Function to invoke to perform work
 *
 * This function sets up a callback to be invoked when either the
 * given actor is mapped, or after some period of time when the machine
 * is idle.  This is useful if your actor isn't always visible on the
 * screen (for example, all actors in the overview), and you don't want
 * to consume resources updating if the actor isn't actually going to be
 * displaying to the user.
 *
 * Note that queueDeferredWork is called by default immediately on
 * initialization as well, under the assumption that new actors
 * will need it.
 *
 * Returns: A string work identifer
 */
function initializeDeferredWork(actor, callback, props) {
    // Turn into a string so we can use as an object property
    let workId = '' + (++_deferredWorkSequence);
    _deferredWorkData[workId] = { 'actor': actor,
                                  'callback': callback };
    actor.connect('notify::mapped', () => {
        if (!(actor.mapped && _deferredWorkQueue.indexOf(workId) >= 0))
            return;
        _queueBeforeRedraw(workId);
    });
    actor.connect('destroy', () => {
        let index = _deferredWorkQueue.indexOf(workId);
        if (index >= 0)
            _deferredWorkQueue.splice(index, 1);
        delete _deferredWorkData[workId];
    });
    queueDeferredWork(workId);
    return workId;
}

/**
 * queueDeferredWork:
 * @workId: work identifier
 *
 * Ensure that the work identified by @workId will be
 * run on map or timeout.  You should call this function
 * for example when data being displayed by the actor has
 * changed.
 */
function queueDeferredWork(workId) {
    let data = _deferredWorkData[workId];
    if (!data) {
        let message = 'Invalid work id %d'.format(workId);
        logError(new Error(message), message);
        return;
    }
    if (_deferredWorkQueue.indexOf(workId) < 0)
        _deferredWorkQueue.push(workId);
    if (data.actor.mapped) {
        _queueBeforeRedraw(workId);
        return;
    } else if (_deferredTimeoutId == 0) {
        _deferredTimeoutId = Mainloop.timeout_add_seconds(DEFERRED_TIMEOUT_SECONDS, () => {
            _runAllDeferredWork();
            _deferredTimeoutId = 0;
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(_deferredTimeoutId, '[gnome-shell] _runAllDeferredWork');
    }
}

var RestartMessage = class extends ModalDialog.ModalDialog {
    constructor(message) {
        super({ shellReactive: true,
                styleClass: 'restart-message headline',
                shouldFadeIn: false,
                destroyOnClose: true });

        let label = new St.Label({ text: message });

        this.contentLayout.add(label, { x_fill: false,
                                        y_fill: false,
                                        x_align: St.Align.MIDDLE,
                                        y_align: St.Align.MIDDLE });
        this.buttonLayout.hide();
    }
};

function showRestartMessage(message) {
    let restartMessage = new RestartMessage(message);
    restartMessage.open();
}

var AnimationsSettings = class {
    constructor() {
        let backend = Meta.get_backend();
        if (!backend.is_rendering_hardware_accelerated()) {
            St.Settings.get().inhibit_animations();
            return;
        }

        let isXvnc = Shell.util_has_x11_display_extension(
            global.display, 'VNC-EXTENSION');
        if (isXvnc) {
            St.Settings.get().inhibit_animations();
            return;
        }

        let remoteAccessController = backend.get_remote_access_controller();
        if (!remoteAccessController)
            return;

        this._handles = new Set();
        remoteAccessController.connect('new-handle',
            (_, handle) => this._onNewRemoteAccessHandle(handle));
    }

    _onRemoteAccessHandleStopped(handle) {
        let settings = St.Settings.get();

        settings.uninhibit_animations();
        this._handles.delete(handle);
    }

    _onNewRemoteAccessHandle(handle) {
        if (!handle.get_disable_animations())
            return;

        let settings = St.Settings.get();

        settings.inhibit_animations();
        this._handles.add(handle);
        handle.connect('stopped', this._onRemoteAccessHandleStopped.bind(this));
    }
};
(uuay)introspect.js�const { Gio, GLib, Meta, Shell, St } = imports.gi;

const INTROSPECT_SCHEMA = 'org.gnome.shell';
const INTROSPECT_KEY = 'introspect';
const APP_WHITELIST = ['org.freedesktop.impl.portal.desktop.gtk'];

const INTROSPECT_DBUS_API_VERSION = 2;

const { loadInterfaceXML } = imports.misc.fileUtils;

const IntrospectDBusIface = loadInterfaceXML('org.gnome.Shell.Introspect');

var IntrospectService = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(IntrospectDBusIface,
                                                             this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Introspect');
        Gio.DBus.session.own_name('org.gnome.Shell.Introspect',
                                  Gio.BusNameOwnerFlags.REPLACE,
                                  null, null);

        this._runningApplications = {};
        this._runningApplicationsDirty = true;
        this._activeApplication = null;
        this._activeApplicationDirty = true;
        this._animationsEnabled = true;

        this._appSystem = Shell.AppSystem.get_default();
        this._appSystem.connect('app-state-changed',
                                () => {
                                    this._runningApplicationsDirty = true;
                                    this._syncRunningApplications();
                                });

        this._introspectSettings = new Gio.Settings({
            schema_id: INTROSPECT_SCHEMA,
        });

        let tracker = Shell.WindowTracker.get_default();
        tracker.connect('notify::focus-app',
                        () => {
                            this._activeApplicationDirty = true;
                            this._syncRunningApplications();
                        });

        this._syncRunningApplications();

        this._whitelistMap = new Map();
        APP_WHITELIST.forEach(appName => {
            Gio.DBus.watch_name(Gio.BusType.SESSION,
                appName,
                Gio.BusNameWatcherFlags.NONE,
                (conn, name, owner) => this._whitelistMap.set(name, owner),
                (conn, name) => this._whitelistMap.delete(name));
        });

        this._settings = St.Settings.get();
        this._settings.connect('notify::enable-animations',
            this._syncAnimationsEnabled.bind(this));
        this._syncAnimationsEnabled();
    }

    _isStandaloneApp(app) {
        let windows = app.get_windows();

        return app.get_windows().some(w => w.transient_for == null);
    }

    _isIntrospectEnabled() {
        return this._introspectSettings.get_boolean(INTROSPECT_KEY);
    }

    _isSenderWhitelisted(sender) {
        return [...this._whitelistMap.values()].includes(sender);
    }

    _getSandboxedAppId(app) {
        let ids = app.get_windows().map(w => w.get_sandboxed_app_id());
        return ids.find(id => id != null);
    }

    _syncRunningApplications() {
        let tracker = Shell.WindowTracker.get_default();
        let apps = this._appSystem.get_running();
        let seatName = "seat0";
        let newRunningApplications = {};

        let newActiveApplication = null;
        let focusedApp = tracker.focus_app;

        for (let app of apps) {
            let appInfo = {};
            let isAppActive = (focusedApp == app);

            if (!this._isStandaloneApp(app))
                continue;

            if (isAppActive) {
                appInfo['active-on-seats'] = new GLib.Variant('as', [seatName]);
                newActiveApplication = app.get_id();
            }

            let sandboxedAppId = this._getSandboxedAppId(app);
            if (sandboxedAppId)
                appInfo['sandboxed-app-id'] = new GLib.Variant('s', sandboxedAppId);

            newRunningApplications[app.get_id()] = appInfo;
        }

        if (this._runningApplicationsDirty ||
            (this._activeApplicationDirty &&
             this._activeApplication != newActiveApplication)) {
            this._runningApplications = newRunningApplications;
            this._activeApplication = newActiveApplication;

            this._dbusImpl.emit_signal('RunningApplicationsChanged', null);
        }
        this._runningApplicationsDirty = false;
        this._activeApplicationDirty = false;
    }

    _isEligibleWindow(window) {
        if (window.is_override_redirect())
            return false;

        let type = window.get_window_type();
        return (type == Meta.WindowType.NORMAL ||
                type == Meta.WindowType.DIALOG ||
                type == Meta.WindowType.MODAL_DIALOG ||
                type == Meta.WindowType.UTILITY);
    }

    _isInvocationAllowed(invocation) {
        if (this._isIntrospectEnabled())
            return true;

        if (this._isSenderWhitelisted(invocation.get_sender()))
            return true;

        return false;
    }

    GetRunningApplicationsAsync(params, invocation) {
        if (!this._isInvocationAllowed(invocation)) {
            invocation.return_error_literal(Gio.DBusError,
                                            Gio.DBusError.ACCESS_DENIED,
                                            'App introspection not allowed');
            return;
        }

        invocation.return_value(new GLib.Variant('(a{sa{sv}})', [this._runningApplications]));
    }

    GetWindowsAsync(params, invocation) {
        let focusWindow = global.display.get_focus_window();
        let apps = this._appSystem.get_running();
        let windowsList = {};

        if (!this._isInvocationAllowed(invocation)) {
            invocation.return_error_literal(Gio.DBusError,
                                            Gio.DBusError.ACCESS_DENIED,
                                            'App introspection not allowed');
            return;
        }

        for (let app of apps) {
            let windows = app.get_windows();
            for (let window of windows) {

                if (!this._isEligibleWindow(window))
                    continue;

                let windowId = window.get_id();
                let frameRect = window.get_frame_rect();
                let title = window.get_title();
                let wmClass = window.get_wm_class();
                let sandboxedAppId = window.get_sandboxed_app_id();

                windowsList[windowId] = {
                    'app-id': GLib.Variant.new('s', app.get_id()),
                    'client-type': GLib.Variant.new('u', window.get_client_type()),
                    'is-hidden': GLib.Variant.new('b', window.is_hidden()),
                    'has-focus': GLib.Variant.new('b', (window == focusWindow)),
                    'width': GLib.Variant.new('u', frameRect.width),
                    'height': GLib.Variant.new('u', frameRect.height)
                };

                // These properties may not be available for all windows:
                if (title != null)
                    windowsList[windowId]['title'] = GLib.Variant.new('s', title);

                if (wmClass != null)
                    windowsList[windowId]['wm-class'] = GLib.Variant.new('s', wmClass);

                if (sandboxedAppId != null)
                    windowsList[windowId]['sandboxed-app-id'] =
                        GLib.Variant.new('s', sandboxedAppId);
            }
        }
        invocation.return_value(new GLib.Variant('(a{ta{sv}})', [windowsList]));
    }

    _syncAnimationsEnabled() {
        let wasAnimationsEnabled = this._animationsEnabled;
        this._animationsEnabled = this._settings.enable_animations;
        if (wasAnimationsEnabled !== this._animationsEnabled) {
            let variant = new GLib.Variant('b', this._animationsEnabled);
            this._dbusImpl.emit_property_changed('AnimationsEnabled', variant);
        }
    }

    get AnimationsEnabled() {
        return this._animationsEnabled;
    }

    get version() {
        return INTROSPECT_DBUS_API_VERSION;
    }
};
(uuay)keyboard.jsj�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, St } = imports.gi;
const Signals = imports.signals;

const InputSourceManager = imports.ui.status.keyboard;
const IBusManager = imports.misc.ibusManager;
const BoxPointer = imports.ui.boxpointer;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const PageIndicators = imports.ui.pageIndicators;
const PopupMenu = imports.ui.popupMenu;
const Tweener = imports.ui.tweener;

var KEYBOARD_REST_TIME = Layout.KEYBOARD_ANIMATION_TIME * 2 * 1000;
var KEY_LONG_PRESS_TIME = 250;
var PANEL_SWITCH_ANIMATION_TIME = 0.5;
var PANEL_SWITCH_RELATIVE_DISTANCE = 1 / 3; /* A third of the actor width */

const A11Y_APPLICATIONS_SCHEMA = 'org.gnome.desktop.a11y.applications';
const SHOW_KEYBOARD = 'screen-keyboard-enabled';

/* KeyContainer puts keys in a grid where a 1:1 key takes this size */
const KEY_SIZE = 2;

const defaultKeysPre = [
    [ [], [], [{ width: 1.5, level: 1, extraClassName: 'shift-key-lowercase' }], [{ label: '?123', width: 1.5, level: 2 }] ],
    [ [], [], [{ width: 1.5, level: 0, extraClassName: 'shift-key-uppercase' }], [{ label: '?123', width: 1.5, level: 2 }] ],
    [ [], [], [{ label: '=/<', width: 1.5, level: 3 }], [{ label: 'ABC', width: 1.5, level: 0 }] ],
    [ [], [], [{ label: '?123', width: 1.5, level: 2 }], [{ label: 'ABC', width: 1.5, level: 0 }] ],
];

const defaultKeysPost = [
    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
      [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
      [{ width: 3, level: 1, right: true, extraClassName: 'shift-key-lowercase' }],
      [{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
      [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
      [{ width: 3, level: 0, right: true, extraClassName: 'shift-key-uppercase' }],
      [{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
      [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
      [{ label: '=/<', width: 3, level: 3, right: true }],
      [{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
    [ [{ label: '⌫', width: 1.5, keyval: Clutter.KEY_BackSpace }],
      [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key' }],
      [{ label: '?123', width: 3, level: 2, right: true }],
      [{ label: '☻', action: 'emoji' }, { action: 'languageMenu', extraClassName: 'layout-key' }, { action: 'hide', extraClassName: 'hide-key' }] ],
];

var AspectContainer = GObject.registerClass(
class AspectContainer extends St.Widget {
    _init(params) {
        super._init(params);
        this._ratio = 1;
    }

    setRatio(relWidth, relHeight) {
        this._ratio = relWidth / relHeight;
        this.queue_relayout();
    }

    vfunc_allocate(box, flags) {
        if (box.get_width() > 0 && box.get_height() > 0) {
            let sizeRatio = box.get_width() / box.get_height();

            if (sizeRatio >= this._ratio) {
                /* Restrict horizontally */
                let width = box.get_height() * this._ratio;
                let diff = box.get_width() - width;

                box.x1 += Math.floor(diff / 2);
                box.x2 -= Math.ceil(diff / 2);
            } else {
                /* Restrict vertically, align to bottom */
                let height = box.get_width() / this._ratio;
                box.y1 = box.y2 - Math.floor(height);
            }
        }

        super.vfunc_allocate(box, flags);
    }
});

var KeyContainer = GObject.registerClass(
class KeyContainer extends St.Widget {
    _init() {
        let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL,
                                                  column_homogeneous: true,
                                                  row_homogeneous: true });
        super._init({ layout_manager: gridLayout,
                      x_expand: true, y_expand: true });
        this._gridLayout = gridLayout;
        this._currentRow = 0;
        this._currentCol = 0;
        this._maxCols = 0;

        this._currentRow = null;
        this._rows = [];
    }

    appendRow(length) {
        this._currentRow++;
        this._currentCol = 0;

        let row = new Object();
        row.keys = [];
        row.width = 0;
        this._rows.push(row);
    }

    appendKey(key, width = 1, height = 1) {
        let keyInfo = {
            key,
            left: this._currentCol,
            top: this._currentRow,
            width,
            height
        };

        let row = this._rows[this._rows.length - 1];
        row.keys.push(keyInfo);
        row.width += width;

        this._currentCol += width;
        this._maxCols = Math.max(this._currentCol, this._maxCols);
    }

    layoutButtons(container) {
        let nCol = 0, nRow = 0;

        for (let i = 0; i < this._rows.length; i++) {
            let row = this._rows[i];

            /* When starting a new row, see if we need some padding */
            if (nCol == 0) {
                let diff = this._maxCols - row.width;
                if (diff >= 1)
                    nCol = diff * KEY_SIZE / 2;
                else
                    nCol = diff * KEY_SIZE;
            }

            for (let j = 0; j < row.keys.length; j++) {
                let keyInfo = row.keys[j];
                let width = keyInfo.width * KEY_SIZE;
                let height = keyInfo.height * KEY_SIZE;

                this._gridLayout.attach(keyInfo.key, nCol, nRow, width, height);
                nCol += width;
            }

            nRow += KEY_SIZE;
            nCol = 0;
        }

        if (container)
            container.setRatio(this._maxCols, this._rows.length);
    }
});

var Suggestions = class {
    constructor() {
        this.actor = new St.BoxLayout({ style_class: 'word-suggestions',
                                        vertical: false });
        this.actor.show();
    }

    add(word, callback) {
        let button = new St.Button({ label: word });
        button.connect('clicked', callback);
        this.actor.add(button);
    }

    clear() {
        this.actor.remove_all_children();
    }
};
Signals.addSignalMethods(Suggestions.prototype);

var LanguageSelectionPopup = class extends PopupMenu.PopupMenu {
    constructor(actor) {
        super(actor, 0.5, St.Side.BOTTOM);

        let inputSourceManager = InputSourceManager.getInputSourceManager();
        let inputSources = inputSourceManager.inputSources;

        let item;
        for (let i in inputSources) {
            let is = inputSources[i];

            item = this.addAction(is.displayName, () => {
                inputSourceManager.activateInputSource(is, true);
            });
            item.actor.can_focus = false;
        }

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        item = this.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop');
        item.actor.can_focus = false;

        this._capturedEventId = 0;

        this._unmapId = actor.connect('notify::mapped', () => {
            if (!actor.is_mapped())
                this.close(true);
        });
    }

    _onCapturedEvent(actor, event) {
        if (event.get_source() == this.actor ||
            this.actor.contains(event.get_source()))
            return Clutter.EVENT_PROPAGATE;

        if (event.type() == Clutter.EventType.BUTTON_RELEASE || event.type() == Clutter.EventType.TOUCH_END)
            this.close(true);

        return Clutter.EVENT_STOP;
    }

    open(animate) {
        super.open(animate);
        this._capturedEventId = global.stage.connect('captured-event',
                                                     this._onCapturedEvent.bind(this));
    }

    close(animate) {
        super.close(animate);
        if (this._capturedEventId != 0) {
            global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
    }

    destroy() {
        if (this._capturedEventId != 0)
            global.stage.disconnect(this._capturedEventId);
        if (this._unmapId != 0)
            this.sourceActor.disconnect(this._unmapId);
        super.destroy();
    }
};

var Key = class Key {
    constructor(key, extendedKeys) {
        this.key = key || "";
        this.keyButton = this._makeKey(this.key);

        /* Add the key in a container, so keys can be padded without losing
         * logical proportions between those.
         */
        this.actor = new St.BoxLayout ({ style_class: 'key-container' });
        this.actor.add(this.keyButton, { expand: true, x_fill: true });
        this.actor.connect('destroy', this._onDestroy.bind(this));

        this._extended_keys = extendedKeys;
        this._extended_keyboard = null;
        this._pressTimeoutId = 0;
        this._capturedPress = false;

        this._capturedEventId = 0;
        this._unmapId = 0;
        this._longPress = false;
    }

    _onDestroy() {
        if (this._boxPointer) {
            this._boxPointer.destroy();
            this._boxPointer = null;
        }

        this.cancel();
    }

    _ensureExtendedKeysPopup() {
        if (this._extended_keys.length == 0)
            return;

        this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM,
                                                     { x_fill: true,
                                                       y_fill: true,
                                                       x_align: St.Align.START });
        this._boxPointer.hide();
        Main.layoutManager.addChrome(this._boxPointer.actor);
        this._boxPointer.setPosition(this.keyButton, 0.5);

        // Adds style to existing keyboard style to avoid repetition
        this._boxPointer.actor.add_style_class_name('keyboard-subkeys');
        this._getExtendedKeys();
        this.keyButton._extended_keys = this._extended_keyboard;
    }

    _getKeyval(key) {
        let unicode = key.charCodeAt(0);
        return Clutter.unicode_to_keysym(unicode);
    }

    _press(key) {
        this.emit('activated')

        if (key != this.key || this._extended_keys.length == 0) {
            this.emit('pressed', this._getKeyval(key), key);
        }

        if (key == this.key) {
            this._pressTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                                                    KEY_LONG_PRESS_TIME,
                                                    () => {
                                                        this._longPress = true;
                                                        this._pressTimeoutId = 0;

                                                        this.emit('long-press');

                                                        if (this._extended_keys.length > 0) {
                                                            this._touchPressed = false;
                                                            this.keyButton.set_hover(false);
                                                            this.keyButton.fake_release();
                                                            this._ensureExtendedKeysPopup();
                                                            this._showSubkeys();
                                                        }

                                                        return GLib.SOURCE_REMOVE;
                                                    });
        }
    }

    _release(key) {
        if (this._pressTimeoutId != 0) {
            GLib.source_remove(this._pressTimeoutId);
            this._pressTimeoutId = 0;
        }

        if (!this._longPress && key == this.key && this._extended_keys.length > 0)
            this.emit('pressed', this._getKeyval(key), key);

        this.emit('released', this._getKeyval(key), key);
        this._hideSubkeys();
        this._longPress = false;
    }

    cancel() {
        if (this._pressTimeoutId != 0) {
            GLib.source_remove(this._pressTimeoutId);
            this._pressTimeoutId = 0;
        }
        this._touchPressed = false;
        this.keyButton.set_hover(false);
        this.keyButton.fake_release();
    }

    _onCapturedEvent(actor, event) {
        let type = event.type();
        let press = (type == Clutter.EventType.BUTTON_PRESS || type == Clutter.EventType.TOUCH_BEGIN);
        let release = (type == Clutter.EventType.BUTTON_RELEASE || type == Clutter.EventType.TOUCH_END);

        if (event.get_source() == this._boxPointer.bin ||
            this._boxPointer.bin.contains(event.get_source()))
            return Clutter.EVENT_PROPAGATE;

        if (press)
            this._capturedPress = true;
        else if (release && this._capturedPress)
            this._hideSubkeys();

        return Clutter.EVENT_STOP;
    }

    _showSubkeys() {
        this._boxPointer.open(BoxPointer.PopupAnimation.FULL);
        this._capturedEventId = global.stage.connect('captured-event',
                                                     this._onCapturedEvent.bind(this));
        this._unmapId = this.keyButton.connect('notify::mapped', () => {
            if (!this.keyButton.is_mapped())
                this._hideSubkeys();
        });
    }

    _hideSubkeys() {
        if (this._boxPointer)
            this._boxPointer.close(BoxPointer.PopupAnimation.FULL);
        if (this._capturedEventId) {
            global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
        if (this._unmapId) {
            this.keyButton.disconnect(this._unmapId);
            this._unmapId = 0;
        }
        this._capturedPress = false;
    }

    _makeKey(key) {
        let label = GLib.markup_escape_text(key, -1);
        let button = new St.Button ({ label: label,
                                      style_class: 'keyboard-key' });

        button.keyWidth = 1;
        button.connect('button-press-event', () => {
            this._press(key);
            return Clutter.EVENT_PROPAGATE;
        });
        button.connect('button-release-event', () => {
            this._release(key);
            return Clutter.EVENT_PROPAGATE;
        });
        button.connect('touch-event', (actor, event) => {
            let device = event.get_device();
            let sequence = event.get_event_sequence();

            // We only handle touch events here on wayland. On X11
            // we do get emulated pointer events, which already works
            // for single-touch cases. Besides, the X11 passive touch grab
            // set up by Mutter will make us see first the touch events
            // and later the pointer events, so it will look like two
            // unrelated series of events, we want to avoid double handling
            // in these cases.
            if (!Meta.is_wayland_compositor())
                return Clutter.EVENT_PROPAGATE;

            if (!this._touchPressed &&
                event.type() == Clutter.EventType.TOUCH_BEGIN) {
                this._touchPressed = true;
                this._press(key);
            } else if (this._touchPressed &&
                       event.type() == Clutter.EventType.TOUCH_END) {
                this._touchPressed = false;
                this._release(key);
            }
            return Clutter.EVENT_PROPAGATE;
        });

        return button;
    }

    _getExtendedKeys() {
        this._extended_keyboard = new St.BoxLayout({ style_class: 'key-container',
                                                     vertical: false });
        for (let i = 0; i < this._extended_keys.length; ++i) {
            let extendedKey = this._extended_keys[i];
            let key = this._makeKey(extendedKey);

            key.extended_key = extendedKey;
            this._extended_keyboard.add(key);

            key.width = this.keyButton.width;
            key.height = this.keyButton.height;
        }
        this._boxPointer.bin.add_actor(this._extended_keyboard);
    }

    get subkeys() {
        return this._boxPointer;
    }

    setWidth(width) {
        this.keyButton.keyWidth = width;
    }

    setLatched(latched) {
        if (latched)
            this.keyButton.add_style_pseudo_class('latched');
        else
            this.keyButton.remove_style_pseudo_class('latched');
    }
};
Signals.addSignalMethods(Key.prototype);

var KeyboardModel = class {
    constructor(groupName) {
        try {
            this._model = this._loadModel(groupName);
        } catch (e) {
            this._model = this._loadModel('us');
        }
    }

    _loadModel(groupName) {
        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/osk-layouts/%s.json'.format(groupName));
        let [success, contents] = file.load_contents(null);
        if (contents instanceof Uint8Array)
            contents = imports.byteArray.toString(contents);

        return JSON.parse(contents);
    }

    getLevels() {
        return this._model.levels;
    }

    getKeysForLevel(levelName) {
        return this._model.levels.find(level => level == levelName);
    }
};

var FocusTracker = class {
    constructor() {
        this._currentWindow = null;
        this._rect = null;

        global.display.connect('notify::focus-window', () => {
            this._setCurrentWindow(global.display.focus_window);
            this.emit('window-changed', this._currentWindow);
        });

        global.display.connect('grab-op-begin', (display, window, op) => {
            if (window == this._currentWindow &&
                (op == Meta.GrabOp.MOVING || op == Meta.GrabOp.KEYBOARD_MOVING))
                this.emit('reset');
        });

        /* Valid for wayland clients */
        Main.inputMethod.connect('cursor-location-changed', (o, rect) => {
            let newRect = { x: rect.get_x(), y: rect.get_y(), width: rect.get_width(), height: rect.get_height() };
            this._setCurrentRect(newRect);
        });

        this._ibusManager = IBusManager.getIBusManager();
        this._ibusManager.connect('set-cursor-location', (manager, rect) => {
            /* Valid for X11 clients only */
            if (Main.inputMethod.currentFocus)
                return;

            this._setCurrentRect(rect);
        });
        this._ibusManager.connect('focus-in', () => {
            this.emit('focus-changed', true);
        });
        this._ibusManager.connect('focus-out', () => {
            this.emit('focus-changed', false);
        });
    }

    get currentWindow() {
        return this._currentWindow;
    }

    _setCurrentWindow(window) {
        this._currentWindow = window;
    }

    _setCurrentRect(rect) {
        if (this._currentWindow) {
            let frameRect = this._currentWindow.get_frame_rect();
            rect.x -= frameRect.x;
            rect.y -= frameRect.y;
        }

        if (this._rect &&
            this._rect.x == rect.x &&
            this._rect.y == rect.y &&
            this._rect.width == rect.width &&
            this._rect.height == rect.height)
            return;

        this._rect = rect;
        this.emit('position-changed');
    }

    getCurrentRect() {
        let rect = { x: this._rect.x, y: this._rect.y,
                     width: this._rect.width, height: this._rect.height };

        if (this._currentWindow) {
            let frameRect = this._currentWindow.get_frame_rect();
            rect.x += frameRect.x;
            rect.y += frameRect.y;
        }

        return rect;
    }
};
Signals.addSignalMethods(FocusTracker.prototype);

var EmojiPager = class EmojiPager {
    constructor(sections, nCols, nRows) {
        this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
                                     reactive: true,
                                     clip_to_allocation: true });
        this._sections = sections;
        this._nCols = nCols;
        this._nRows = nRows;

        this._pages = [];
        this._panel = null;
        this._curPage = null;
        this._followingPage = null;
        this._followingPanel = null;
        this._currentKey = null;
        this._delta = 0;
        this._width = null;

        this._initPagingInfo();

        let panAction = new Clutter.PanAction({ interpolate: false });
        panAction.connect('pan', this._onPan.bind(this));
        panAction.connect('gesture-begin', this._onPanBegin.bind(this));
        panAction.connect('gesture-cancel', this._onPanCancel.bind(this));
        panAction.connect('gesture-end', this._onPanEnd.bind(this));
        this._panAction = panAction;
        this.actor.add_action(panAction);
    }

    get delta() {
        return this._delta;
    }

    set delta(value) {
        if (value > this._width)
            value = this._width;
        else if (value < -this._width)
            value = -this._width;

        this._delta = value;

        if (value == 0)
            return;

        let relValue = Math.abs(value / this._width);
        let followingPage = this.getFollowingPage();

        if (this._followingPage != followingPage) {
            if (this._followingPanel) {
                this._followingPanel.destroy();
                this._followingPanel = null;
            }

            if (followingPage != null) {
                this._followingPanel = this._generatePanel(followingPage);
                this._followingPanel.set_pivot_point(0.5, 0.5);
                this.actor.add_child(this._followingPanel);
                this.actor.set_child_below_sibling(this._followingPanel, this._panel);
            }

            this._followingPage = followingPage;
        }

        this._panel.translation_x = value;
        this._panel.opacity = 255 * (1 - Math.pow(relValue, 3));

        if (this._followingPanel) {
            this._followingPanel.scale_x = 0.8 + (0.2 * relValue);
            this._followingPanel.scale_y = 0.8 + (0.2 * relValue);
            this._followingPanel.opacity = 255 * relValue;
        }
    }

    _prevPage(nPage) {
        return (nPage + this._pages.length - 1) % this._pages.length;
    }

    _nextPage(nPage) {
        return (nPage + 1) % this._pages.length;
    }

    getFollowingPage() {
        if (this.delta == 0)
            return null;

        if ((this.delta < 0 && global.stage.text_direction == Clutter.TextDirection.LTR) ||
            (this.delta > 0 && global.stage.text_direction == Clutter.TextDirection.RTL))
            return this._nextPage(this._curPage);
        else
            return this._prevPage(this._curPage);
    }

    _onPan(action) {
        let [dist, dx, dy] = action.get_motion_delta(0);
        this.delta = this.delta + dx;

        if (this._currentKey != null) {
            this._currentKey.cancel();
            this._currentKey = null;
        }

        return false;
    }

    _onPanBegin() {
        this._width = this.actor.width;
        return true;
    }

    _onPanEnd() {
        if (Math.abs(this._delta) < this.actor.width * PANEL_SWITCH_RELATIVE_DISTANCE) {
            this._onPanCancel()
        } else {
            let value;
            if (this._delta > 0)
                value = this._width;
            else if (this._delta < 0)
                value = -this._width;

            let relDelta = Math.abs(this._delta - value) / this._width;
            let time = PANEL_SWITCH_ANIMATION_TIME * Math.abs(relDelta);

            Tweener.removeTweens(this);
            Tweener.addTween(this,
                             { delta: value,
                               time: time,
                               transition: 'easeInOutQuad',
                               onComplete() {
                                   this.setCurrentPage(this.getFollowingPage());
                               }
                             });
        }
    }

    _onPanCancel() {
        let relDelta = Math.abs(this._delta) / this.actor.width;
        let time = PANEL_SWITCH_ANIMATION_TIME * Math.abs(relDelta);

        Tweener.removeTweens(this);
        Tweener.addTween(this,
                         { delta: 0,
                           time: time,
                           transition: 'easeInOutQuad',
                         });
    }

    _initPagingInfo() {
        for (let i = 0; i < this._sections.length; i++) {
            let section = this._sections[i];
            let itemsPerPage = this._nCols * this._nRows;
            let nPages = Math.ceil(section.keys.length / itemsPerPage);
            let page = -1;
            let pageKeys;

            for (let j = 0; j < section.keys.length; j++) {
                if (j % itemsPerPage == 0) {
                    page++;
                    pageKeys = [];
                    this._pages.push({ pageKeys, nPages, page, section: this._sections[i] });
                }

                pageKeys.push(section.keys[j]);
            }
        }
    }

    _lookupSection(section, nPage) {
        for (let i = 0; i < this._pages.length; i++) {
            let page = this._pages[i];

            if (page.section == section && page.page == nPage)
                return i;
        }

        return -1;
    }

    _generatePanel(nPage) {
        let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL,
                                                  column_homogeneous: true,
                                                  row_homogeneous: true });
        let panel = new St.Widget({ layout_manager: gridLayout,
                                    style_class: 'emoji-page',
                                    x_expand: true,
                                    y_expand: true });

        /* Set an expander actor so all proportions are right despite the panel
         * not having all rows/cols filled in.
         */
        let expander = new Clutter.Actor();
        gridLayout.attach(expander, 0, 0, this._nCols, this._nRows);

        let page = this._pages[nPage];
        let col = 0;
        let row = 0;

        for (let i = 0; i < page.pageKeys.length; i++) {
            let modelKey = page.pageKeys[i];
            let key = new Key(modelKey.label, modelKey.variants);

            key.keyButton.set_button_mask(0);

            key.connect('activated', () => {
                this._currentKey = key;
            });
            key.connect('long-press', () => {
                this._panAction.cancel();
            });
            key.connect('released', (actor, keyval, str) => {
                if (this._currentKey != key)
                    return;
                this._currentKey = null;
                this.emit('emoji', str);
            });

            gridLayout.attach(key.actor, col, row, 1, 1);

            col++;
            if (col >= this._nCols) {
                col = 0;
                row++;
            }
        }

        return panel;
    }

    setCurrentPage(nPage) {
        if (this._curPage == nPage)
            return;

        this._curPage = nPage;

        if (this._panel) {
            this._panel.destroy();
            this._panel = null;
        }

        /* Reuse followingPage if possible */
        if (nPage == this._followingPage) {
            this._panel = this._followingPanel;
            this._followingPanel = null;
        }

        if (this._followingPanel)
            this._followingPanel.destroy();

        this._followingPanel = null;
        this._followingPage = null;
        this._delta = 0;

        if (!this._panel) {
            this._panel = this._generatePanel(nPage);
            this.actor.add_child(this._panel);
        }

        let page = this._pages[nPage];
        this.emit('page-changed', page.section, page.page, page.nPages);
    }

    setCurrentSection(section, nPage) {
        for (let i = 0; i < this._pages.length; i++) {
            let page = this._pages[i];

            if (page.section == section && page.page == nPage) {
                this.setCurrentPage(i);
                break;
            }
        }
    }
};
Signals.addSignalMethods(EmojiPager.prototype);

var EmojiSelection = class EmojiSelection {
    constructor() {
        this._sections = [
            { first: 'grinning face', label: '🙂️' },
            { first: 'selfie', label: '👍️' },
            { first: 'monkey face', label: '🌷️' },
            { first: 'grapes', label: '🍴️' },
            { first: 'globe showing Europe-Africa', label: '✈️' },
            { first: 'jack-o-lantern', label: '🏃️' },
            { first: 'muted speaker', label: '🔔️' },
            { first: 'ATM sign', label: '❤️' },
            { first: 'chequered flag', label: '🚩️' },
        ];

        this._populateSections();

        this.actor = new St.BoxLayout({ style_class: 'emoji-panel',
                                        x_expand: true,
                                        y_expand: true,
                                        vertical: true });
        this.actor.connect('notify::mapped', () => { this._emojiPager.setCurrentPage(0); });

        this._emojiPager = new EmojiPager(this._sections, 11, 3);
        this._emojiPager.connect('page-changed', (pager, section, page, nPages) => {
            this._onPageChanged(section, page, nPages);
        });
        this._emojiPager.connect('emoji', (pager, str) => {
            this.emit('emoji-selected', str);
        });
        this.actor.add(this._emojiPager.actor, { expand: true });

        this._pageIndicator = new PageIndicators.PageIndicators(false);
        this.actor.add(this._pageIndicator, { expand: true, x_fill: false, y_fill: false });
        this._pageIndicator.setReactive(false);

        let bottomRow = this._createBottomRow();
        this.actor.add(bottomRow, { expand: true, x_fill: false, y_fill: false });

        this._emojiPager.setCurrentPage(0);
    }

    _onPageChanged(section, page, nPages) {
        this._pageIndicator.setNPages(nPages);
        this._pageIndicator.setCurrentPage(page);

        for (let i = 0; i < this._sections.length; i++) {
            let sect = this._sections[i];
            sect.button.setLatched(section == sect);
        }
    }

    _findSection(emoji) {
        for (let i = 0; i < this._sections.length; i++) {
            if (this._sections[i].first == emoji)
                return this._sections[i];
        }

        return null;
    }

    _populateSections() {
        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/osk-layouts/emoji.json');
        let [success, contents] = file.load_contents(null);

        if (contents instanceof Uint8Array)
            contents = imports.byteArray.toString(contents);
        let emoji = JSON.parse(contents);

        let pages = [];
        let variants = [];
        let currentKey = 0;
        let currentSection = null;

        for (let i = 0; i < emoji.length; i++) {
            /* Group variants of a same emoji so they appear on the key popover */
            if (emoji[i].name.startsWith(emoji[currentKey].name)) {
                variants.push(emoji[i].char);
                if (i < emoji.length - 1)
                    continue;
            }

            let newSection = this._findSection(emoji[currentKey].name);
            if (newSection != null) {
                currentSection = newSection;
                currentSection.keys = [];
            }

            /* Create the key */
            let label = emoji[currentKey].char + String.fromCharCode(0xFE0F);
            currentSection.keys.push({ label, variants });
            currentKey = i;
            variants = [];
        }
    }

    _createBottomRow() {
        let row = new KeyContainer();
        let key;

        row.appendRow();

        key = new Key('ABC', []);
        key.keyButton.add_style_class_name('default-key');
        key.connect('released', () => { this.emit('toggle'); });
        row.appendKey(key.actor, 1.5);

        for (let i = 0; i < this._sections.length; i++) {
            let section = this._sections[i];

            key = new Key(section.label, []);
            key.connect('released', () => { this._emojiPager.setCurrentSection(section, 0) });
            row.appendKey(key.actor);

            section.button = key;
        }

        key = new Key(null, []);
        key.keyButton.add_style_class_name('default-key');
        key.keyButton.add_style_class_name('hide-key');
        key.connect('released', () => {
            this.emit('hide');
        });
        row.appendKey(key.actor);
        row.layoutButtons();

        let actor = new AspectContainer({ layout_manager: new Clutter.BinLayout(),
                                          x_expand: true, y_expand: true });
        actor.add_child(row);
        /* Regular keyboard layouts are 11.5×4 grids, optimize for that
         * at the moment. Ideally this should be as wide as the current
         * keymap.
         */
        actor.setRatio(11.5, 1);

        return actor;
    }
};
Signals.addSignalMethods(EmojiSelection.prototype);

var Keypad = class Keypad {
    constructor() {
        let keys = [
            { label: '1', keyval: Clutter.KEY_1, left: 0, top: 0 },
            { label: '2', keyval: Clutter.KEY_2, left: 1, top: 0 },
            { label: '3', keyval: Clutter.KEY_3, left: 2, top: 0 },
            { label: '4', keyval: Clutter.KEY_4, left: 0, top: 1 },
            { label: '5', keyval: Clutter.KEY_5, left: 1, top: 1 },
            { label: '6', keyval: Clutter.KEY_6, left: 2, top: 1 },
            { label: '7', keyval: Clutter.KEY_7, left: 0, top: 2 },
            { label: '8', keyval: Clutter.KEY_8, left: 1, top: 2 },
            { label: '9', keyval: Clutter.KEY_9, left: 2, top: 2 },
            { label: '0', keyval: Clutter.KEY_0, left: 1, top: 3 },
            { label: '⌫', keyval: Clutter.KEY_BackSpace, left: 3, top: 0 },
            { keyval: Clutter.KEY_Return, extraClassName: 'enter-key', left: 3, top: 1, height: 2 },
        ];

        this.actor = new AspectContainer({ layout_manager: new Clutter.BinLayout(),
                                           x_expand: true, y_expand: true });

        let gridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.HORIZONTAL,
                                                  column_homogeneous: true,
                                                  row_homogeneous: true });
        this._box = new St.Widget({ layout_manager: gridLayout, x_expand: true, y_expand: true });
        this.actor.add_child(this._box);

        for (let i = 0; i < keys.length; i++) {
            let cur = keys[i];
            let key = new Key(cur.label || "", []);

            if (keys[i].extraClassName)
                key.keyButton.add_style_class_name(cur.extraClassName);

            let w, h;
            w = cur.width || 1;
            h = cur.height || 1;
            gridLayout.attach(key.actor, cur.left, cur.top, w, h);

            key.connect('released', () => {
                this.emit('keyval', cur.keyval);
            });
        }
    }
};
Signals.addSignalMethods(Keypad.prototype);

var Keyboard = class Keyboard {
    constructor() {
        this.actor = null;
        this._focusInExtendedKeys = false;
        this._emojiActive = false;

        this._languagePopup = null;
        this._currentFocusWindow = null;
        this._animFocusedWindow = null;
        this._delayedAnimFocusWindow = null;

        this._enableKeyboard = false; // a11y settings value
        this._enabled = false; // enabled state (by setting or device type)
        this._latched = false; // current level is latched

        this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA });
        this._a11yApplicationsSettings.connect('changed', this._syncEnabled.bind(this));
        this._lastDeviceId = null;
        this._suggestions = null;
        this._emojiKeyVisible = true;

        let manager = Clutter.DeviceManager.get_default();
        manager.connect('notify::touch-mode', this._syncEnabled.bind(this));

        this._focusTracker = new FocusTracker();
        this._focusTracker.connect('position-changed', this._onFocusPositionChanged.bind(this));
        this._focusTracker.connect('reset', () => {
            this._delayedAnimFocusWindow = null;
            this._animFocusedWindow = null;
            this._oskFocusWindow = null;
        });
        this._focusTracker.connect('focus-changed', (tracker, focused) => {
            // Valid only for X11
            if (Meta.is_wayland_compositor())
                return;

            if (focused)
                this.show(Main.layoutManager.focusIndex);
            else
                this.hide();
        });

        Meta.get_backend().connect('last-device-changed', 
            (backend, deviceId) => {
                let manager = Clutter.DeviceManager.get_default();
                let device = manager.get_device(deviceId);

                if (device.get_device_name().indexOf('XTEST') < 0) {
                    if (device.device_type == Clutter.InputDeviceType.KEYBOARD_DEVICE)
                        return;

                    this._lastDeviceId = deviceId;
                    this._syncEnabled();
                }
            });
        this._syncEnabled();

        this._showIdleId = 0;

        this._keyboardVisible = false;
        Main.layoutManager.connect('keyboard-visible-changed', (o, visible) => {
            this._keyboardVisible = visible;
        });
        this._keyboardRequested = false;
        this._keyboardRestingId = 0;

        Main.layoutManager.connect('monitors-changed', this._relayout.bind(this));
    }

    get visible() {
        return this._keyboardVisible;
    }

    _onFocusPositionChanged(focusTracker) {
        let rect = focusTracker.getCurrentRect();
        this.setCursorLocation(focusTracker.currentWindow, rect.x, rect.y, rect.width, rect.height);
    }

    _lastDeviceIsTouchscreen() {
        if (!this._lastDeviceId)
            return false;

        let manager = Clutter.DeviceManager.get_default();
        let device = manager.get_device(this._lastDeviceId);

        if (!device)
            return false;

        return device.get_device_type() == Clutter.InputDeviceType.TOUCHSCREEN_DEVICE;
    }

    _syncEnabled() {
        let wasEnabled = this._enabled;
        let manager = Clutter.DeviceManager.get_default();
        let autoEnabled = manager.get_touch_mode() && this._lastDeviceIsTouchscreen();
        this._enableKeyboard = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD);
        this._enabled = this._enableKeyboard || autoEnabled;
        if (!this._enabled && !this._keyboardController)
            return;

        if (this._enabled && !this._keyboardController)
            this._setupKeyboard();
        else if (!this._enabled)
            this.setCursorLocation(null);

        if (!this._enabled && wasEnabled)
            Main.layoutManager.hideKeyboard(true);
    }

    _destroyKeyboard() {
        if (this._keyboardNotifyId)
            this._keyboardController.disconnect(this._keyboardNotifyId);
        if (this._keyboardGroupsChangedId)
            this._keyboardController.disconnect(this._keyboardGroupsChangedId);
        if (this._keyboardStateId)
            this._keyboardController.disconnect(this._keyboardStateId);
        if (this._emojiKeyVisibleId)
            this._keyboardController.disconnect(this._emojiKeyVisibleId);
        if (this._keypadVisibleId)
            this._keyboardController.disconnect(this._keypadVisibleId);
        if (this._focusNotifyId)
            global.stage.disconnect(this._focusNotifyId);
        this._keyboard = null;
        this.actor.destroy();
        this.actor = null;

        if (this._languagePopup) {
            this._languagePopup.destroy();
            this._languagePopup = null;
        }
    }

    _setupKeyboard() {
        this.actor = new St.BoxLayout({ name: 'keyboard', vertical: true, reactive: true });
        Main.layoutManager.keyboardBox.add_actor(this.actor);
        Main.layoutManager.trackChrome(this.actor);

        this._keyboardController = new KeyboardController();

        this._groups = {};
        this._current_page = null;

        this._suggestions = new Suggestions();
        this.actor.add(this._suggestions.actor,
                       { x_align: St.Align.MIDDLE,
                         x_fill: false });

        this._aspectContainer = new AspectContainer({ layout_manager: new Clutter.BinLayout() });
        this.actor.add(this._aspectContainer, { expand: true });

        this._emojiSelection = new EmojiSelection();
        this._emojiSelection.connect('toggle', this._toggleEmoji.bind(this));
        this._emojiSelection.connect('hide', (selection) => { this.hide(); });
        this._emojiSelection.connect('emoji-selected', (selection, emoji) => {
            this._keyboardController.commitString(emoji);
        });

        this._aspectContainer.add_child(this._emojiSelection.actor);
        this._emojiSelection.actor.hide();

        this._keypad = new Keypad();
        this._keypad.connect('keyval', (keypad, keyval) => {
            this._keyboardController.keyvalPress(keyval);
            this._keyboardController.keyvalRelease(keyval);
        });
        this._aspectContainer.add_child(this._keypad.actor);
        this._keypad.actor.hide();
        this._keypadVisible = false;

        this._ensureKeysForGroup(this._keyboardController.getCurrentGroup());
        this._setActiveLayer(0);

        // Keyboard models are defined in LTR, we must override
        // the locale setting in order to avoid flipping the
        // keyboard on RTL locales.
        this.actor.text_direction = Clutter.TextDirection.LTR;

        this._keyboardNotifyId = this._keyboardController.connect('active-group', this._onGroupChanged.bind(this));
        this._keyboardGroupsChangedId = this._keyboardController.connect('groups-changed', this._onKeyboardGroupsChanged.bind(this));
        this._keyboardStateId = this._keyboardController.connect('panel-state', this._onKeyboardStateChanged.bind(this));
        this._emojiKeyVisibleId = this._keyboardController.connect('emoji-visible', this._onEmojiKeyVisible.bind(this));
        this._keypadVisibleId = this._keyboardController.connect('keypad-visible', this._onKeypadVisible.bind(this));
        this._focusNotifyId = global.stage.connect('notify::key-focus', this._onKeyFocusChanged.bind(this));

        this._relayout();
    }

    _onKeyFocusChanged() {
        let focus = global.stage.key_focus;

        // Showing an extended key popup and clicking a key from the extended keys
        // will grab focus, but ignore that
        let extendedKeysWereFocused = this._focusInExtendedKeys;
        this._focusInExtendedKeys = focus && (focus._extended_keys || focus.extended_key);
        if (this._focusInExtendedKeys || extendedKeysWereFocused)
            return;

        if (!(focus instanceof Clutter.Text)) {
            this.hide();
            return;
        }

        if (!this._showIdleId) {
          this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
              this.show(Main.layoutManager.focusIndex);
              return GLib.SOURCE_REMOVE;
          });
          GLib.Source.set_name_by_id(this._showIdleId, '[gnome-shell] this.show');
        }
    }

    _createLayersForGroup(groupName) {
        let keyboardModel = new KeyboardModel(groupName);
        let layers = {};
        let levels = keyboardModel.getLevels();
        for (let i = 0; i < levels.length; i++) {
            let currentLevel = levels[i];
            /* There are keyboard maps which consist of 3 levels (no uppercase,
             * basically). We however make things consistent by skipping that
             * second level.
             */
            let level = (i >= 1 && levels.length == 3) ? i + 1 : i;

            let layout = new KeyContainer();
            layout.shiftKeys = [];

            this._loadRows(currentLevel, level, levels.length, layout);
            layers[level] = layout;
            this._aspectContainer.add_child(layout);
            layout.layoutButtons(this._aspectContainer);

            layout.hide();
        }

        return layers;
    }

    _ensureKeysForGroup(group) {
        if (!this._groups[group])
            this._groups[group] = this._createLayersForGroup(group);
    }

    _addRowKeys(keys, layout) {
        for (let i = 0; i < keys.length; ++i) {
            let key = keys[i];
            let button = new Key(key.shift(), key);

            /* Space key gets special width, dependent on the number of surrounding keys */
            if (button.key == ' ')
                button.setWidth(keys.length <= 3 ? 5 : 3);

            button.connect('pressed', (actor, keyval, str) => {
                if (!Main.inputMethod.currentFocus ||
                    !this._keyboardController.commitString(str, true)) {
                    if (keyval != 0) {
                        this._keyboardController.keyvalPress(keyval);
                        button._keyvalPress = true;
                    }
                }
            });
            button.connect('released', (actor, keyval, str) => {
                if (keyval != 0) {
                    if (button._keyvalPress)
                        this._keyboardController.keyvalRelease(keyval);
                    button._keyvalPress = false;
                }

                if (!this._latched)
                    this._setActiveLayer(0);
            });

            layout.appendKey(button.actor, button.keyButton.keyWidth);
        }
    }

    _popupLanguageMenu(keyActor) {
        if (this._languagePopup)
            this._languagePopup.destroy();

        this._languagePopup = new LanguageSelectionPopup(keyActor);
        Main.layoutManager.addChrome(this._languagePopup.actor);
        this._languagePopup.open(true);
    }

    _loadDefaultKeys(keys, layout, numLevels, numKeys) {
        let extraButton;
        for (let i = 0; i < keys.length; i++) {
            let key = keys[i];
            let keyval = key.keyval;
            let switchToLevel = key.level;
            let action = key.action;

            /* Skip emoji button if necessary */
            if (!this._emojiKeyVisible && action == 'emoji')
                continue;

            extraButton = new Key(key.label || '', []);

            extraButton.keyButton.add_style_class_name('default-key');
            if (key.extraClassName != null)
                extraButton.keyButton.add_style_class_name(key.extraClassName);
            if (key.width != null)
                extraButton.setWidth(key.width);

            let actor = extraButton.keyButton;

            extraButton.connect('pressed', () => {
                if (switchToLevel != null) {
                    this._setActiveLayer(switchToLevel);
                    // Shift only gets latched on long press
                    this._latched = (switchToLevel != 1);
                } else if (keyval != null) {
                    this._keyboardController.keyvalPress(keyval);
                }
            });
            extraButton.connect('released', () => {
                if (keyval != null)
                    this._keyboardController.keyvalRelease(keyval);
                else if (action == 'hide')
                    this.hide();
                else if (action == 'languageMenu')
                    this._popupLanguageMenu(actor);
                else if (action == 'emoji')
                    this._toggleEmoji();
            });

            if (switchToLevel == 0) {
                layout.shiftKeys.push(extraButton);
            } else if (switchToLevel == 1) {
                extraButton.connect('long-press', () => {
                    this._latched = true;
                    this._setCurrentLevelLatched(this._current_page, this._latched);
                });
            }

            /* Fixup default keys based on the number of levels/keys */
            if (switchToLevel == 1 && numLevels == 3) {
                // Hide shift key if the keymap has no uppercase level
                if (key.right) {
                    /* Only hide the key actor, so the container still takes space */
                    extraButton.keyButton.hide();
                } else {
                    extraButton.actor.hide();
                }
                extraButton.setWidth(1.5);
            } else if (key.right && numKeys > 8) {
                extraButton.setWidth(2);
            } else if (keyval == Clutter.KEY_Return && numKeys > 9) {
                extraButton.setWidth(1.5);
            } else if (!this._emojiKeyVisible && (action == 'hide' || action == 'languageMenu')) {
                extraButton.setWidth(1.5);
            }

            layout.appendKey(extraButton.actor, extraButton.keyButton.keyWidth);
        }
    }

    _updateCurrentPageVisible() {
        if (this._current_page)
            this._current_page.visible = !this._emojiActive && !this._keypadVisible;
    }

    _setEmojiActive(active) {
        this._emojiActive = active;
        this._emojiSelection.actor.visible = this._emojiActive;
        this._updateCurrentPageVisible();
    }

    _toggleEmoji() {
        this._setEmojiActive(!this._emojiActive);
    }

    _setCurrentLevelLatched(layout, latched) {
        for (let i = 0; i < layout.shiftKeys.length; i++) {
            let key = layout.shiftKeys[i];
            key.setLatched(latched);
        }
    }

    _getDefaultKeysForRow(row, numRows, level) {
        let pre, post;

        /* The first 2 rows in defaultKeysPre/Post belong together with
         * the first 2 rows on each keymap. On keymaps that have more than
         * 4 rows, the last 2 default key rows must be respectively
         * assigned to the 2 last keymap ones.
         */
        if (row < 2) {
            return [defaultKeysPre[level][row], defaultKeysPost[level][row]];
        } else if (row >= numRows - 2) {
            let defaultRow = row - (numRows - 2) + 2;
            return [defaultKeysPre[level][defaultRow], defaultKeysPost[level][defaultRow]];
        } else {
            return [null, null];
        }
    }

    _mergeRowKeys(layout, pre, row, post, numLevels) {
        if (pre != null)
            this._loadDefaultKeys(pre, layout, numLevels, row.length);

        this._addRowKeys(row, layout);

        if (post != null)
            this._loadDefaultKeys(post, layout, numLevels, row.length);
    }

    _loadRows(model, level, numLevels, layout) {
        let rows = model.rows;
        for (let i = 0; i < rows.length; ++i) {
            layout.appendRow();
            let [pre, post] = this._getDefaultKeysForRow(i, rows.length, level);
            this._mergeRowKeys (layout, pre, rows[i], post, numLevels);
        }
    }

    _getGridSlots() {
        let numOfHorizSlots = 0, numOfVertSlots;
        let rows = this._current_page.get_children();
        numOfVertSlots = rows.length;

        for (let i = 0; i < rows.length; ++i) {
            let keyboard_row = rows[i];
            let keys = keyboard_row.get_children();

            numOfHorizSlots = Math.max(numOfHorizSlots, keys.length);
        }

        return [numOfHorizSlots, numOfVertSlots];
    }

    _relayout() {
        let monitor = Main.layoutManager.keyboardMonitor;

        if (this.actor == null || monitor == null)
            return;

        let maxHeight = monitor.height / 3;
        this.actor.width = monitor.width;
        this.actor.height = maxHeight;
    }

    _onGroupChanged() {
        this._ensureKeysForGroup(this._keyboardController.getCurrentGroup());
        this._setActiveLayer(0);
    }

    _onKeyboardGroupsChanged(keyboard) {
        let nonGroupActors = [this._emojiSelection.actor, this._keypad.actor];
        this._aspectContainer.get_children().filter(c => !nonGroupActors.includes(c)).forEach(c => {
            c.destroy();
        });

        this._groups = {};
        this._onGroupChanged();
    }

    _onKeypadVisible(controller, visible) {
        if (visible == this._keypadVisible)
            return;

        this._keypadVisible = visible;
        this._keypad.actor.visible = this._keypadVisible;
        this._updateCurrentPageVisible();
    }

    _onEmojiKeyVisible(controller, visible) {
        if (visible == this._emojiKeyVisible)
            return;

        this._emojiKeyVisible = visible;
        /* Rebuild keyboard widgetry to include emoji button */
        this._onKeyboardGroupsChanged();
    }

    _onKeyboardStateChanged(controller, state) {
        let enabled;
        if (state == Clutter.InputPanelState.OFF)
            enabled = false;
        else if (state == Clutter.InputPanelState.ON)
            enabled = true;
        else if (state == Clutter.InputPanelState.TOGGLE)
            enabled = (this._keyboardVisible == false);
        else
            return;

        if (enabled)
            this.show(Main.layoutManager.focusIndex);
        else
            this.hide();
    }

    _setActiveLayer(activeLevel) {
        let activeGroupName = this._keyboardController.getCurrentGroup();
        let layers = this._groups[activeGroupName];

        if (this._current_page != null) {
            this._setCurrentLevelLatched(this._current_page, false);
            this._current_page.hide();
        }

        this._current_page = layers[activeLevel];
        this._updateCurrentPageVisible();
    }

    shouldTakeEvent(event) {
        let actor = event.get_source();
        return Main.layoutManager.keyboardBox.contains(actor) ||
               !!actor._extended_keys || !!actor.extended_key;
    }

    _clearKeyboardRestTimer() {
        if (!this._keyboardRestingId)
            return;
        GLib.source_remove(this._keyboardRestingId);
        this._keyboardRestingId = 0;
    }

    show(monitor) {
        if (!this._enabled)
            return;

        this._clearShowIdle();
        this._keyboardRequested = true;

        if (this._keyboardVisible) {
            if (monitor != Main.layoutManager.keyboardIndex) {
                Main.layoutManager.keyboardIndex = monitor;
                this._relayout();
            }
            return;
        }

        this._clearKeyboardRestTimer();
        this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                                                   KEYBOARD_REST_TIME,
                                                   () => {
                                                       this._clearKeyboardRestTimer();
                                                       this._show(monitor);
                                                       return GLib.SOURCE_REMOVE;
                                                   });
        GLib.Source.set_name_by_id(this._keyboardRestingId, '[gnome-shell] this._clearKeyboardRestTimer');
    }

    _show(monitor) {
        if (!this._keyboardRequested)
            return;

        Main.layoutManager.keyboardIndex = monitor;
        this._relayout();
        Main.layoutManager.showKeyboard();

        this._setEmojiActive(false);

        if (this._delayedAnimFocusWindow) {
            this._setAnimationWindow(this._delayedAnimFocusWindow);
            this._delayedAnimFocusWindow = null;
        }
    }

    hide() {
        if (!this._enabled)
            return;

        this._clearShowIdle();
        this._keyboardRequested = false;

        if (!this._keyboardVisible)
            return;

        this._clearKeyboardRestTimer();
        this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                                                   KEYBOARD_REST_TIME,
                                                   () => {
                                                       this._clearKeyboardRestTimer();
                                                       this._hide();
                                                       return GLib.SOURCE_REMOVE;
                                                   });
        GLib.Source.set_name_by_id(this._keyboardRestingId, '[gnome-shell] this._clearKeyboardRestTimer');
    }

    _hide() {
        if (this._keyboardRequested)
            return;

        Main.layoutManager.hideKeyboard();
        this.setCursorLocation(null);
    }

    _hideSubkeys() {
        if (this._subkeysBoxPointer) {
            this._subkeysBoxPointer.hide(BoxPointer.PopupAnimation.FULL);
            this._subkeysBoxPointer = null;
        }
        if (this._capturedEventId) {
            this.actor.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
        this._capturedPress = false;
    }

    resetSuggestions() {
        if (this._suggestions)
            this._suggestions.clear();
    }

    addSuggestion(text, callback) {
        if (!this._suggestions)
            return;
        this._suggestions.add(text, callback);
        this._suggestions.actor.show();
    }

    _clearShowIdle() {
        if (!this._showIdleId)
            return;
        GLib.source_remove(this._showIdleId);
        this._showIdleId = 0;
    }

    _windowSlideAnimationComplete(window, delta) {
        // Synchronize window and actor positions again.
        let windowActor = window.get_compositor_private();
        let frameRect = window.get_frame_rect();
        frameRect.y += delta;
        window.move_frame(true, frameRect.x, frameRect.y);
    }

    _animateWindow(window, show) {
        let windowActor = window.get_compositor_private();
        let deltaY = Main.layoutManager.keyboardBox.height;
        if (!windowActor)
            return;

        if (show) {
            Tweener.addTween(windowActor,
                             { y: windowActor.y - deltaY,
                               time: Layout.KEYBOARD_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: this._windowSlideAnimationComplete,
                               onCompleteParams: [window, -deltaY] });
        } else {
            Tweener.addTween(windowActor,
                             { y: windowActor.y + deltaY,
                               time: Layout.KEYBOARD_ANIMATION_TIME,
                               transition: 'easeInQuad',
                               onComplete: this._windowSlideAnimationComplete,
                               onCompleteParams: [window, deltaY] });
        }
    }

    _setAnimationWindow(window) {
        if (this._animFocusedWindow == window)
            return;

        if (this._animFocusedWindow)
            this._animateWindow(this._animFocusedWindow, false);
        if (window)
            this._animateWindow(window, true);

        this._animFocusedWindow = window;
    }

    setCursorLocation(window, x, y , w, h) {
        let monitor = Main.layoutManager.keyboardMonitor;

        if (window && monitor) {
            let keyboardHeight = Main.layoutManager.keyboardBox.height;
            let focusObscured = false;

            if (y + h >= monitor.y + monitor.height - keyboardHeight) {
                if (this._keyboardVisible)
                    this._setAnimationWindow(window);
                else
                    this._delayedAnimFocusWindow = window;
            } else if (y < keyboardHeight) {
                this._delayedAnimFocusWindow = null;
                this._setAnimationWindow(null);
            }
        } else {
            this._setAnimationWindow(null);
        }

        this._oskFocusWindow = window;
    }
};

var KeyboardController = class {
    constructor() {
        let deviceManager = Clutter.DeviceManager.get_default();
        this._virtualDevice = deviceManager.create_virtual_device(Clutter.InputDeviceType.KEYBOARD_DEVICE);

        this._inputSourceManager = InputSourceManager.getInputSourceManager();
        this._sourceChangedId = this._inputSourceManager.connect('current-source-changed',
                                                                 this._onSourceChanged.bind(this));
        this._sourcesModifiedId = this._inputSourceManager.connect ('sources-changed',
                                                                    this._onSourcesModified.bind(this));
        this._currentSource = this._inputSourceManager.currentSource;

        Main.inputMethod.connect('notify::content-purpose',
                                 this._onContentPurposeHintsChanged.bind(this));
        Main.inputMethod.connect('notify::content-hints',
                                 this._onContentPurposeHintsChanged.bind(this));
        Main.inputMethod.connect('input-panel-state', (o, state) => {
            this.emit('panel-state', state);
        });
    }

    _onSourcesModified() {
        this.emit('groups-changed');
    }

    _onSourceChanged(inputSourceManager, oldSource) {
        let source = inputSourceManager.currentSource;
        this._currentSource = source;
        this.emit('active-group', source.id);
    }

    _onContentPurposeHintsChanged(method) {
        let hints = method.content_hints;
        let purpose = method.content_purpose;
        let emojiVisible = false;
        let keypadVisible = false;

        if (purpose == Clutter.InputContentPurpose.NORMAL ||
            purpose == Clutter.InputContentPurpose.ALPHA ||
            purpose == Clutter.InputContentPurpose.PASSWORD ||
            purpose == Clutter.InputContentPurpose.TERMINAL)
            emojiVisible = true;
        if (purpose == Clutter.InputContentPurpose.DIGITS ||
            purpose == Clutter.InputContentPurpose.NUMBER ||
            purpose == Clutter.InputContentPurpose.PHONE)
            keypadVisible = true;

        this.emit('emoji-visible', emojiVisible)
        this.emit('keypad-visible', keypadVisible);
    }

    getGroups() {
        let inputSources = this._inputSourceManager.inputSources;
        let groups = []

        for (let i in inputSources) {
            let is = inputSources[i];
            groups[is.index] = is.xkbId;
        }

        return groups;
    }

    getCurrentGroup() {
        return this._currentSource.xkbId;
    }

    commitString(string, fromKey) {
        if (string == null)
            return false;
        /* Let ibus methods fall through keyval emission */
        if (fromKey && this._currentSource.type == InputSourceManager.INPUT_SOURCE_TYPE_IBUS)
            return false;

        Main.inputMethod.commit(string);
        return true;
    }

    keyvalPress(keyval) {
        this._virtualDevice.notify_keyval(Clutter.get_current_event_time(),
                                          keyval, Clutter.KeyState.PRESSED);
    }

    keyvalRelease(keyval) {
        this._virtualDevice.notify_keyval(Clutter.get_current_event_time(),
                                          keyval, Clutter.KeyState.RELEASED);
    }
};
Signals.addSignalMethods(KeyboardController.prototype);
(uuay)power.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, St, UPowerGlib: UPower } = imports.gi;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const { loadInterfaceXML } = imports.misc.fileUtils;

const BUS_NAME = 'org.freedesktop.UPower';
const OBJECT_PATH = '/org/freedesktop/UPower/devices/DisplayDevice';

const DisplayDeviceInterface = loadInterfaceXML('org.freedesktop.UPower.Device');
const PowerManagerProxy = Gio.DBusProxy.makeProxyWrapper(DisplayDeviceInterface);

const SHOW_BATTERY_PERCENTAGE       = 'show-battery-percentage';

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
        this._desktopSettings.connect('changed::' + SHOW_BATTERY_PERCENTAGE,
                                      this._sync.bind(this));

        this._indicator = this._addIndicator();
        this._percentageLabel = new St.Label({ y_expand: true,
                                               y_align: Clutter.ActorAlign.CENTER });
        this.indicators.add(this._percentageLabel, { expand: true, y_fill: true });
        this.indicators.add_style_class_name('power-status');

        this._proxy = new PowerManagerProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
                                            (proxy, error) => {
                                                if (error) {
                                                    log(error.message);
                                                    return;
                                                }
                                                this._proxy.connect('g-properties-changed',
                                                                    this._sync.bind(this));
                                                this._sync();
                                            });

        this._item = new PopupMenu.PopupSubMenuMenuItem("", true);
        this._item.menu.addSettingsAction(_("Power Settings"), 'gnome-power-panel.desktop');
        this.menu.addMenuItem(this._item);

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    _sessionUpdated() {
        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        this.menu.setSensitive(sensitive);
    }

    _getStatus() {
        let seconds = 0;

        if (this._proxy.State == UPower.DeviceState.FULLY_CHARGED)
            return _("Fully Charged");
        else if (this._proxy.State == UPower.DeviceState.CHARGING)
            seconds = this._proxy.TimeToFull;
        else if (this._proxy.State == UPower.DeviceState.DISCHARGING)
            seconds = this._proxy.TimeToEmpty;
        else if (this._proxy.State == UPower.DeviceState.PENDING_CHARGE)
            return _("Not Charging");
        // state is PENDING_DISCHARGE
        else
            return _("Estimating…");

        let time = Math.round(seconds / 60);
        if (time == 0) {
            // 0 is reported when UPower does not have enough data
            // to estimate battery life
            return _("Estimating…");
        }

        let minutes = time % 60;
        let hours = Math.floor(time / 60);

        if (this._proxy.State == UPower.DeviceState.DISCHARGING) {
            // Translators: this is <hours>:<minutes> Remaining (<percentage>)
            return _("%d\u2236%02d Remaining (%d\u2009%%)").format(hours, minutes, this._proxy.Percentage);
        }

        if (this._proxy.State == UPower.DeviceState.CHARGING) {
            // Translators: this is <hours>:<minutes> Until Full (<percentage>)
            return _("%d\u2236%02d Until Full (%d\u2009%%)").format(hours, minutes, this._proxy.Percentage);
        }

        return null;
    }

    _sync() {
        // Do we have batteries or a UPS?
        let visible = this._proxy.IsPresent;
        if (visible) {
            this._item.actor.show();
            this._percentageLabel.visible = this._desktopSettings.get_boolean(SHOW_BATTERY_PERCENTAGE);
        } else {
            // If there's no battery, then we use the power icon.
            this._item.actor.hide();
            this._indicator.icon_name = 'system-shutdown-symbolic';
            this._percentageLabel.hide();
            return;
        }

        // The icons
        let icon = this._proxy.IconName;
        this._indicator.icon_name = icon;
        this._item.icon.icon_name = icon;

        // The icon label
        let label
        if (this._proxy.State == UPower.DeviceState.FULLY_CHARGED)
          label = _("%d\u2009%%").format(100);
        else
          label = _("%d\u2009%%").format(this._proxy.Percentage);
        this._percentageLabel.clutter_text.set_markup('<span size="smaller">' + label + '</span>');

        // The status label
        this._item.label.text = this._getStatus();
    }
};
(uuay)search.js{f// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
const Signals = imports.signals;

const AppDisplay = imports.ui.appDisplay;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const RemoteSearch = imports.ui.remoteSearch;
const Util = imports.misc.util;

const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';

var MAX_LIST_SEARCH_RESULTS_ROWS = 5;
var MAX_GRID_SEARCH_RESULTS_ROWS = 1;

var MaxWidthBin = GObject.registerClass(
class MaxWidthBin extends St.Bin {
    vfunc_allocate(box, flags) {
        let themeNode = this.get_theme_node();
        let maxWidth = themeNode.get_max_width();
        let availWidth = box.x2 - box.x1;
        let adjustedBox = box;

        if (availWidth > maxWidth) {
            let excessWidth = availWidth - maxWidth;
            adjustedBox.x1 += Math.floor(excessWidth / 2);
            adjustedBox.x2 -= Math.floor(excessWidth / 2);
        }

        super.vfunc_allocate(adjustedBox, flags);
    }
});

var SearchResult = class {
    constructor(provider, metaInfo, resultsView) {
        this.provider = provider;
        this.metaInfo = metaInfo;
        this._resultsView = resultsView;

        this.actor = new St.Button({ reactive: true,
                                     can_focus: true,
                                     track_hover: true,
                                     x_align: St.Align.START,
                                     y_fill: true });

        this.actor._delegate = this;
        this.actor.connect('clicked', this.activate.bind(this));
    }

    activate() {
        this.emit('activate', this.metaInfo.id);
    }
};
Signals.addSignalMethods(SearchResult.prototype);

var ListSearchResult = class extends SearchResult {

    constructor(provider, metaInfo, resultsView) {
        super(provider, metaInfo, resultsView);

        this.actor.style_class = 'list-search-result';
        this.actor.x_fill = true;

        let content = new St.BoxLayout({ style_class: 'list-search-result-content',
                                         vertical: false });
        this.actor.set_child(content);

        this._termsChangedId = 0;

        let titleBox = new St.BoxLayout({ style_class: 'list-search-result-title' });

        content.add(titleBox, { x_fill: true,
                                y_fill: false,
                                x_align: St.Align.START,
                                y_align: St.Align.MIDDLE });

        // An icon for, or thumbnail of, content
        let icon = this.metaInfo['createIcon'](this.ICON_SIZE);
        if (icon) {
            titleBox.add(icon);
        }

        let title = new St.Label({ text: this.metaInfo['name'] });
        titleBox.add(title, { x_fill: false,
                              y_fill: false,
                              x_align: St.Align.START,
                              y_align: St.Align.MIDDLE });

        this.actor.label_actor = title;

        if (this.metaInfo['description']) {
            this._descriptionLabel = new St.Label({ style_class: 'list-search-result-description' });
            content.add(this._descriptionLabel, { x_fill: false,
                                                  y_fill: false,
                                                  x_align: St.Align.START,
                                                  y_align: St.Align.MIDDLE });

            this._termsChangedId =
                this._resultsView.connect('terms-changed',
                                          this._highlightTerms.bind(this));

            this._highlightTerms();
        }

        this.actor.connect('destroy', this._onDestroy.bind(this));
    }

    get ICON_SIZE() {
        return 24;
    }

    _highlightTerms() {
        let markup = this._resultsView.highlightTerms(this.metaInfo['description'].split('\n')[0]);
        this._descriptionLabel.clutter_text.set_markup(markup);
    }

    _onDestroy() {
        if (this._termsChangedId)
            this._resultsView.disconnect(this._termsChangedId);
        this._termsChangedId = 0;
    }
};

var GridSearchResult = class extends SearchResult {
    constructor(provider, metaInfo, resultsView) {
        super(provider, metaInfo, resultsView);

        this.actor.style_class = 'grid-search-result';

        this.icon = new IconGrid.BaseIcon(this.metaInfo['name'],
                                          { createIcon: this.metaInfo['createIcon'] });
        let content = new St.Bin({ child: this.icon });
        this.actor.set_child(content);
        this.actor.label_actor = this.icon.label;
    }
};

var SearchResultsBase = class {
    constructor(provider, resultsView) {
        this.provider = provider;
        this._resultsView = resultsView;

        this._terms = [];

        this.actor = new St.BoxLayout({ style_class: 'search-section',
                                        vertical: true });

        this._resultDisplayBin = new St.Bin({ x_fill: true,
                                              y_fill: true });
        this.actor.add(this._resultDisplayBin, { expand: true });

        let separator = new St.Widget({ style_class: 'search-section-separator' });
        this.actor.add(separator);

        this._resultDisplays = {};

        this._clipboard = St.Clipboard.get_default();

        this._cancellable = new Gio.Cancellable();
    }

    destroy() {
        this.actor.destroy();
        this._terms = [];
    }

    _createResultDisplay(meta) {
        if (this.provider.createResultObject)
            return this.provider.createResultObject(meta, this._resultsView);

        return null;
    }

    clear() {
        this._cancellable.cancel();
        for (let resultId in this._resultDisplays)
            this._resultDisplays[resultId].actor.destroy();
        this._resultDisplays = {};
        this._clearResultDisplay();
        this.actor.hide();
    }

    _keyFocusIn(actor) {
        this.emit('key-focus-in', actor);
    }

    _activateResult(result, id) {
        this.provider.activateResult(id, this._terms);
        if (result.metaInfo.clipboardText)
            this._clipboard.set_text(St.ClipboardType.CLIPBOARD, result.metaInfo.clipboardText);
        Main.overview.toggle();
    }

    _setMoreCount(count) {
    }

    _ensureResultActors(results, callback) {
        let metasNeeded = results.filter(
            resultId => this._resultDisplays[resultId] === undefined
        );

        if (metasNeeded.length === 0) {
            callback(true);
        } else {
            this._cancellable.cancel();
            this._cancellable.reset();

            this.provider.getResultMetas(metasNeeded, metas => {
                if (this._cancellable.is_cancelled()) {
                    if (metas.length > 0)
                        log(`Search provider ${this.provider.id} returned results after the request was canceled`);
                    callback(false);
                    return;
                }
                if (metas.length != metasNeeded.length) {
                    log('Wrong number of result metas returned by search provider ' + this.provider.id +
                        ': expected ' + metasNeeded.length + ' but got ' + metas.length);
                    callback(false);
                    return;
                }
                if (metas.some(meta => !meta.name || !meta.id)) {
                    log('Invalid result meta returned from search provider ' + this.provider.id);
                    callback(false);
                    return;
                }

                metasNeeded.forEach((resultId, i) => {
                    let meta = metas[i];
                    let display = this._createResultDisplay(meta);
                    display.connect('activate', this._activateResult.bind(this));
                    display.actor.connect('key-focus-in', this._keyFocusIn.bind(this));
                    this._resultDisplays[resultId] = display;
                });
                callback(true);
            }, this._cancellable);
        }
    }

    updateSearch(providerResults, terms, callback) {
        this._terms = terms;
        if (providerResults.length == 0) {
            this._clearResultDisplay();
            this.actor.hide();
            callback();
        } else {
            let maxResults = this._getMaxDisplayedResults();
            let results = this.provider.filterResults(providerResults, maxResults);
            let moreCount = Math.max(providerResults.length - results.length, 0);

            this._ensureResultActors(results, successful => {
                if (!successful) {
                    this._clearResultDisplay();
                    callback();
                    return;
                }

                // To avoid CSS transitions causing flickering when
                // the first search result stays the same, we hide the
                // content while filling in the results.
                this.actor.hide();
                this._clearResultDisplay();
                results.forEach(resultId => {
                    this._addItem(this._resultDisplays[resultId]);
                });
                this._setMoreCount(this.provider.canLaunchSearch ? moreCount : 0);
                this.actor.show();
                callback();
            });
        }
    }
};

var ListSearchResults = class extends SearchResultsBase {
    constructor(provider, resultsView) {
        super(provider, resultsView);

        this._container = new St.BoxLayout({ style_class: 'search-section-content' });
        this.providerInfo = new ProviderInfo(provider);
        this.providerInfo.connect('key-focus-in', this._keyFocusIn.bind(this));
        this.providerInfo.connect('clicked', () => {
            this.providerInfo.animateLaunch();
            provider.launchSearch(this._terms);
            Main.overview.toggle();
        });

        this._container.add(this.providerInfo, { x_fill: false,
                                                 y_fill: false,
                                                 x_align: St.Align.START,
                                                 y_align: St.Align.START });

        this._content = new St.BoxLayout({ style_class: 'list-search-results',
                                           vertical: true });
        this._container.add(this._content, { expand: true });

        this._resultDisplayBin.set_child(this._container);
    }

    _setMoreCount(count) {
        this.providerInfo.setMoreCount(count);
    }

    _getMaxDisplayedResults() {
        return MAX_LIST_SEARCH_RESULTS_ROWS;
    }

    _clearResultDisplay() {
        this._content.remove_all_children();
    }

    _createResultDisplay(meta) {
        return super._createResultDisplay(meta, this._resultsView) ||
               new ListSearchResult(this.provider, meta, this._resultsView);
    }

    _addItem(display) {
        this._content.add_actor(display.actor);
    }

    getFirstResult() {
        if (this._content.get_n_children() > 0)
            return this._content.get_child_at_index(0)._delegate;
        else
            return null;
    }
};
Signals.addSignalMethods(ListSearchResults.prototype);

var GridSearchResults = class extends SearchResultsBase {
    constructor(provider, resultsView) {
        super(provider, resultsView);
        // We need to use the parent container to know how much results we can show.
        // None of the actors in this class can be used for that, since the main actor
        // goes hidden when no results are displayed, and then it lost its allocation.
        // Then on the next use of _getMaxDisplayedResults allocation is 0, en therefore
        // it doesn't show any result although we have some.
        this._parentContainer = resultsView.actor;

        this._grid = new IconGrid.IconGrid({ rowLimit: MAX_GRID_SEARCH_RESULTS_ROWS,
                                             xAlign: St.Align.START });

        this._bin = new St.Bin({ x_align: St.Align.MIDDLE });
        this._bin.set_child(this._grid);

        this._resultDisplayBin.set_child(this._bin);
    }

    _getMaxDisplayedResults() {
        let parentThemeNode = this._parentContainer.get_theme_node();
        let availableWidth = parentThemeNode.adjust_for_width(this._parentContainer.width);
        return this._grid.columnsForWidth(availableWidth) * this._grid.getRowLimit();
    }

    _clearResultDisplay() {
        this._grid.removeAll();
    }

    _createResultDisplay(meta) {
        return super._createResultDisplay(meta, this._resultsView) ||
               new GridSearchResult(this.provider, meta, this._resultsView);
    }

    _addItem(display) {
        this._grid.addItem(display);
    }

    getFirstResult() {
        if (this._grid.visibleItemsCount() > 0)
            return this._grid.getItemAtIndex(0)._delegate;
        else
            return null;
    }
};
Signals.addSignalMethods(GridSearchResults.prototype);

var SearchResults = class {
    constructor() {
        this.actor = new St.BoxLayout({ name: 'searchResults',
                                        vertical: true });

        this._content = new St.BoxLayout({ name: 'searchResultsContent',
                                           vertical: true });
        this._contentBin = new MaxWidthBin({ name: 'searchResultsBin',
                                             x_fill: true,
                                             y_fill: true,
                                             child: this._content });

        let scrollChild = new St.BoxLayout();
        scrollChild.add(this._contentBin, { expand: true });

        this._scrollView = new St.ScrollView({ x_fill: true,
                                               y_fill: false,
                                               overlay_scrollbars: true,
                                               style_class: 'search-display vfade' });
        this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        this._scrollView.add_actor(scrollChild);
        let action = new Clutter.PanAction({ interpolate: true });
        action.connect('pan', this._onPan.bind(this));
        this._scrollView.add_action(action);

        this.actor.add(this._scrollView, { x_fill: true,
                                           y_fill: true,
                                           expand: true,
                                           x_align: St.Align.START,
                                           y_align: St.Align.START });

        this._statusText = new St.Label({ style_class: 'search-statustext' });
        this._statusBin = new St.Bin({ x_align: St.Align.MIDDLE,
                                       y_align: St.Align.MIDDLE });
        this.actor.add(this._statusBin, { expand: true });
        this._statusBin.add_actor(this._statusText);

        this._highlightDefault = false;
        this._defaultResult = null;
        this._startingSearch = false;

        this._terms = [];
        this._results = {};

        this._providers = [];

        this._highlightRegex = null;

        this._searchSettings = new Gio.Settings({ schema_id: SEARCH_PROVIDERS_SCHEMA });
        this._searchSettings.connect('changed::disabled', this._reloadRemoteProviders.bind(this));
        this._searchSettings.connect('changed::enabled', this._reloadRemoteProviders.bind(this));
        this._searchSettings.connect('changed::disable-external', this._reloadRemoteProviders.bind(this));
        this._searchSettings.connect('changed::sort-order', this._reloadRemoteProviders.bind(this));

        this._searchTimeoutId = 0;
        this._cancellable = new Gio.Cancellable();

        this._registerProvider(new AppDisplay.AppSearchProvider());
        this._reloadRemoteProviders();
    }

    _reloadRemoteProviders() {
        let remoteProviders = this._providers.filter(p => p.isRemoteProvider);
        remoteProviders.forEach(provider => {
            this._unregisterProvider(provider);
        });

        RemoteSearch.loadRemoteSearchProviders(this._searchSettings, providers => {
            providers.forEach(this._registerProvider.bind(this));
        });
    }

    _registerProvider(provider) {
        provider.searchInProgress = false;
        this._providers.push(provider);
        this._ensureProviderDisplay(provider);
    }

    _unregisterProvider(provider) {
        let index = this._providers.indexOf(provider);
        this._providers.splice(index, 1);

        if (provider.display)
            provider.display.destroy();
    }

    _gotResults(results, provider) {
        this._results[provider.id] = results;
        this._updateResults(provider, results);
    }

    _clearSearchTimeout() {
        if (this._searchTimeoutId > 0) {
            GLib.source_remove(this._searchTimeoutId);
            this._searchTimeoutId = 0;
        }
    }

    _reset() {
        this._terms = [];
        this._results = {};
        this._clearDisplay();
        this._clearSearchTimeout();
        this._defaultResult = null;
        this._startingSearch = false;

        this._updateSearchProgress();
    }

    _doSearch() {
        this._startingSearch = false;

        let previousResults = this._results;
        this._results = {};

        this._providers.forEach(provider => {
            provider.searchInProgress = true;

            let previousProviderResults = previousResults[provider.id];
            if (this._isSubSearch && previousProviderResults)
                provider.getSubsearchResultSet(previousProviderResults,
                                               this._terms,
                                               results => {
                                                   this._gotResults(results, provider);
                                               },
                                               this._cancellable);
            else
                provider.getInitialResultSet(this._terms,
                                             results => {
                                                 this._gotResults(results, provider);
                                             },
                                             this._cancellable);
        });

        this._updateSearchProgress();

        this._clearSearchTimeout();
    }

    _onSearchTimeout() {
        this._searchTimeoutId = 0;
        this._doSearch();
        return GLib.SOURCE_REMOVE;
    }

    setTerms(terms) {
        // Check for the case of making a duplicate previous search before
        // setting state of the current search or cancelling the search.
        // This will prevent incorrect state being as a result of a duplicate
        // search while the previous search is still active.
        let searchString = terms.join(' ');
        let previousSearchString = this._terms.join(' ');
        if (searchString == previousSearchString)
            return;

        this._startingSearch = true;

        this._cancellable.cancel();
        this._cancellable.reset();

        if (terms.length == 0) {
            this._reset();
            return;
        }

        let isSubSearch = false;
        if (this._terms.length > 0)
            isSubSearch = searchString.indexOf(previousSearchString) == 0;

        this._terms = terms;
        this._isSubSearch = isSubSearch;
        this._updateSearchProgress();

        if (this._searchTimeoutId == 0)
            this._searchTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 150, this._onSearchTimeout.bind(this));

        let escapedTerms = this._terms.map(term => Shell.util_regex_escape(term));
        this._highlightRegex = new RegExp(`(${escapedTerms.join('|')})`, 'gi');

        this.emit('terms-changed');
    }

    _onPan(action) {
        let [dist, dx, dy] = action.get_motion_delta(0);
        let adjustment = this._scrollView.vscroll.adjustment;
        adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
        return false;
    }

    _keyFocusIn(provider, actor) {
        Util.ensureActorVisibleInScrollView(this._scrollView, actor);
    }

    _ensureProviderDisplay(provider) {
        if (provider.display)
            return;

        let providerDisplay;
        if (provider.appInfo)
            providerDisplay = new ListSearchResults(provider, this);
        else
            providerDisplay = new GridSearchResults(provider, this);

        providerDisplay.connect('key-focus-in', this._keyFocusIn.bind(this));
        providerDisplay.actor.hide();
        this._content.add(providerDisplay.actor);
        provider.display = providerDisplay;
    }

    _clearDisplay() {
        this._providers.forEach(provider => {
            provider.display.clear();
        });
    }

    _maybeSetInitialSelection() {
        let newDefaultResult = null;

        let providers = this._providers;
        for (let i = 0; i < providers.length; i++) {
            let provider = providers[i];
            let display = provider.display;

            if (!display.actor.visible)
                continue;

            let firstResult = display.getFirstResult();
            if (firstResult) {
                newDefaultResult = firstResult;
                break; // select this one!
            }
        }

        if (newDefaultResult != this._defaultResult) {
            this._setSelected(this._defaultResult, false);
            this._setSelected(newDefaultResult, this._highlightDefault);

            this._defaultResult = newDefaultResult;
        }
    }

    get searchInProgress() {
        if (this._startingSearch)
            return true;

        return this._providers.some(p => p.searchInProgress);
    }

    _updateSearchProgress() {
        let haveResults = this._providers.some(provider => {
            let display = provider.display;
            return (display.getFirstResult() != null);
        });

        this._scrollView.visible = haveResults;
        this._statusBin.visible = !haveResults;

        if (!haveResults) {
            if (this.searchInProgress) {
                this._statusText.set_text(_("Searching…"));
            } else {
                this._statusText.set_text(_("No results."));
            }
        }
    }

    _updateResults(provider, results) {
        let terms = this._terms;
        let display = provider.display;

        display.updateSearch(results, terms, () => {
            provider.searchInProgress = false;

            this._maybeSetInitialSelection();
            this._updateSearchProgress();
        });
    }

    activateDefault() {
        // If we have a search queued up, force the search now.
        if (this._searchTimeoutId > 0)
            this._doSearch();

        if (this._defaultResult)
            this._defaultResult.activate();
    }

    highlightDefault(highlight) {
        this._highlightDefault = highlight;
        this._setSelected(this._defaultResult, highlight);
    }

    popupMenuDefault() {
        // If we have a search queued up, force the search now.
        if (this._searchTimeoutId > 0)
            this._doSearch();

        if (this._defaultResult)
            this._defaultResult.actor.popup_menu();
    }

    navigateFocus(direction) {
        let rtl = this.actor.get_text_direction() == Clutter.TextDirection.RTL;
        if (direction == St.DirectionType.TAB_BACKWARD ||
            direction == (rtl ? St.DirectionType.RIGHT
                              : St.DirectionType.LEFT) ||
            direction == St.DirectionType.UP) {
            this.actor.navigate_focus(null, direction, false);
            return;
        }

        let from = this._defaultResult ? this._defaultResult.actor : null;
        this.actor.navigate_focus(from, direction, false);
    }

    _setSelected(result, selected) {
        if (!result)
            return;

        if (selected) {
            result.actor.add_style_pseudo_class('selected');
            Util.ensureActorVisibleInScrollView(this._scrollView, result.actor);
        } else {
            result.actor.remove_style_pseudo_class('selected');
        }
    }

    highlightTerms(description) {
        if (!description)
            return '';

        if (!this._highlightRegex)
            return description;

        return description.replace(this._highlightRegex, '<b>$1</b>');
    }
};
Signals.addSignalMethods(SearchResults.prototype);

var ProviderInfo = GObject.registerClass(
class ProviderInfo extends St.Button {
    _init(provider) {
        this.provider = provider;
        super._init({ style_class: 'search-provider-icon',
                      reactive: true,
                      can_focus: true,
                      accessible_name: provider.appInfo.get_name(),
                      track_hover: true });

        this._content = new St.BoxLayout({ vertical: false,
                                           style_class: 'list-search-provider-content' });
        this.set_child(this._content);

        let icon = new St.Icon({ icon_size: this.PROVIDER_ICON_SIZE,
                                 gicon: provider.appInfo.get_icon() });

        let detailsBox = new St.BoxLayout({ style_class: 'list-search-provider-details',
                                            vertical: true,
                                            x_expand: true });

        let nameLabel = new St.Label({ text: provider.appInfo.get_name(),
                                       x_align: Clutter.ActorAlign.START });

        this._moreLabel = new St.Label({ x_align: Clutter.ActorAlign.START });

        detailsBox.add_actor(nameLabel);
        detailsBox.add_actor(this._moreLabel);


        this._content.add_actor(icon);
        this._content.add_actor(detailsBox);
    }

    get PROVIDER_ICON_SIZE() {
        return 32;
    }

    animateLaunch() {
        let appSys = Shell.AppSystem.get_default();
        let app = appSys.lookup_app(this.provider.appInfo.get_id());
        if (app.state == Shell.AppState.STOPPED)
            IconGrid.zoomOutActor(this._content);
    }

    setMoreCount(count) {
        this._moreLabel.text = ngettext("%d more", "%d more", count).format(count);
        this._moreLabel.visible = count > 0;
    }
});
(uuay)background.jsp// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

// READ THIS FIRST
// Background handling is a maze of objects, both objects in this file, and
// also objects inside Mutter. They all have a role.
//
// BackgroundManager
//   The only object that other parts of GNOME Shell deal with; a
//   BackgroundManager creates background actors and adds them to
//   the specified container. When the background is changed by the
//   user it will fade out the old actor and fade in the new actor.
//   (This is separate from the fading for an animated background,
//   since using two actors is quite inefficient.)
//
// MetaBackgroundImage
//   An object represented an image file that will be used for drawing
//   the background. MetaBackgroundImage objects asynchronously load,
//   so they are first created in an unloaded state, then later emit
//   a ::loaded signal when the Cogl object becomes available.
//
// MetaBackgroundImageCache
//   A cache from filename to MetaBackgroundImage.
//
// BackgroundSource
//   An object that is created for each GSettings schema (separate
//   settings schemas are used for the lock screen and main background),
//   and holds a reference to shared Background objects.
//
// MetaBackground
//   Holds the specification of a background - a background color
//   or gradient and one or two images blended together.
//
// Background
//   JS delegate object that Connects a MetaBackground to the GSettings
//   schema for the background.
//
// Animation
//   A helper object that handles loading a XML-based animation; it is a
//   wrapper for GnomeDesktop.BGSlideShow
//
// MetaBackgroundActor
//   An actor that draws the background for a single monitor
//
// BackgroundCache
//   A cache of Settings schema => BackgroundSource and of a single Animation.
//   Also used to share file monitors.
//
// A static image, background color or gradient is relatively straightforward. The
// calling code creates a separate BackgroundManager for each monitor. Since they
// are created for the same GSettings schema, they will use the same BackgroundSource
// object, which provides a single Background and correspondingly a single
// MetaBackground object.
//
// BackgroundManager               BackgroundManager
//        |        \               /        |
//        |         BackgroundSource        |        looked up in BackgroundCache
//        |                |                |
//        |            Background           |
//        |                |                |
//   MetaBackgroundActor   |    MetaBackgroundActor
//         \               |               /
//          `------- MetaBackground ------'
//                         |
//                MetaBackgroundImage            looked up in MetaBackgroundImageCache
//
// The animated case is tricker because the animation XML file can specify different
// files for different monitor resolutions and aspect ratios. For this reason,
// the BackgroundSource provides different Background share a single Animation object,
// which tracks the animation, but use different MetaBackground objects. In the
// common case, the different MetaBackground objects will be created for the
// same filename and look up the *same* MetaBackgroundImage object, so there is
// little wasted memory:
//
// BackgroundManager               BackgroundManager
//        |        \               /        |
//        |         BackgroundSource        |        looked up in BackgroundCache
//        |             /      \            |
//        |     Background   Background     |
//        |       |     \      /   |        |
//        |       |    Animation   |        |        looked up in BackgroundCache
// MetaBackgroundA|tor           Me|aBackgroundActor
//         \      |                |       /
//      MetaBackground           MetaBackground
//                 \                 /
//                MetaBackgroundImage            looked up in MetaBackgroundImageCache
//                MetaBackgroundImage
//
// But the case of different filenames and different background images
// is possible as well:
//                        ....
//      MetaBackground              MetaBackground
//             |                          |
//     MetaBackgroundImage         MetaBackgroundImage
//     MetaBackgroundImage         MetaBackgroundImage

const { Clutter, GDesktopEnums, Gio, GLib, GnomeDesktop, Meta } = imports.gi;
const Signals = imports.signals;

const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

var DEFAULT_BACKGROUND_COLOR = Clutter.Color.from_pixel(0x2e3436ff);

const BACKGROUND_SCHEMA = 'org.gnome.desktop.background';
const PRIMARY_COLOR_KEY = 'primary-color';
const SECONDARY_COLOR_KEY = 'secondary-color';
const COLOR_SHADING_TYPE_KEY = 'color-shading-type';
const BACKGROUND_STYLE_KEY = 'picture-options';
const PICTURE_OPACITY_KEY = 'picture-opacity';
const PICTURE_URI_KEY = 'picture-uri';

var FADE_ANIMATION_TIME = 1.0;

// These parameters affect how often we redraw.
// The first is how different (percent crossfaded) the slide show
// has to look before redrawing and the second is the minimum
// frequency (in seconds) we're willing to wake up
var ANIMATION_OPACITY_STEP_INCREMENT = 4.0;
var ANIMATION_MIN_WAKEUP_INTERVAL = 1.0;

let _backgroundCache = null;

function _fileEqual0(file1, file2) {
    if (file1 == file2)
        return true;

    if (!file1 || !file2)
        return false;

    return file1.equal(file2);
}

var BackgroundCache = class BackgroundCache {
    constructor() {
        this._fileMonitors = {};
        this._backgroundSources = {};
        this._animations = {};
    }

    monitorFile(file) {
        let key = file.hash();
        if (this._fileMonitors[key])
            return;

        let monitor = file.monitor(Gio.FileMonitorFlags.NONE, null);
        monitor.connect('changed',
                        (obj, file, otherFile, eventType) => {
                            // Ignore CHANGED and CREATED events, since in both cases
                            // we'll get a CHANGES_DONE_HINT event when done.
                            if (eventType != Gio.FileMonitorEvent.CHANGED &&
                                eventType != Gio.FileMonitorEvent.CREATED)
                                this.emit('file-changed', file);
                        });

        this._fileMonitors[key] = monitor;
    }

    getAnimation(params) {
        params = Params.parse(params, { file: null,
                                        settingsSchema: null,
                                        onLoaded: null });

        let animation = this._animations[params.settingsSchema];
        if (animation && _fileEqual0(animation.file, params.file)) {
            if (params.onLoaded) {
                let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                    params.onLoaded(this._animations[params.settingsSchema]);
                    return GLib.SOURCE_REMOVE;
                });
                GLib.Source.set_name_by_id(id, '[gnome-shell] params.onLoaded');
            }
            return;
        }

        animation = new Animation({ file: params.file });

        animation.load(() => {
            this._animations[params.settingsSchema] = animation;

            if (params.onLoaded) {
                let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                    params.onLoaded(this._animations[params.settingsSchema]);
                    return GLib.SOURCE_REMOVE;
                });
                GLib.Source.set_name_by_id(id, '[gnome-shell] params.onLoaded');
            }
        });
    }

    getBackgroundSource(layoutManager, settingsSchema) {
        // The layoutManager is always the same one; we pass in it since
        // Main.layoutManager may not be set yet

        if (!(settingsSchema in this._backgroundSources)) {
            this._backgroundSources[settingsSchema] = new BackgroundSource(layoutManager, settingsSchema);
            this._backgroundSources[settingsSchema]._useCount = 1;
        } else {
            this._backgroundSources[settingsSchema]._useCount++;
        }

        return this._backgroundSources[settingsSchema];
    }

    releaseBackgroundSource(settingsSchema) {
        if (settingsSchema in this._backgroundSources) {
            let source = this._backgroundSources[settingsSchema];
            source._useCount--;
            if (source._useCount == 0) {
                delete this._backgroundSources[settingsSchema];
                source.destroy();
            }
        }
    }
};
Signals.addSignalMethods(BackgroundCache.prototype);

function getBackgroundCache() {
    if (!_backgroundCache)
        _backgroundCache = new BackgroundCache();
    return _backgroundCache;
}

var Background = class Background {
    constructor(params) {
        params = Params.parse(params, { monitorIndex: 0,
                                        layoutManager: Main.layoutManager,
                                        settings: null,
                                        file: null,
                                        style: null });

        this.background = new Meta.Background({ meta_display: global.display });
        this.background._delegate = this;

        this._settings = params.settings;
        this._file = params.file;
        this._style = params.style;
        this._monitorIndex = params.monitorIndex;
        this._layoutManager = params.layoutManager;
        this._fileWatches = {};
        this._cancellable = new Gio.Cancellable();
        this.isLoaded = false;

        this._clock = new GnomeDesktop.WallClock();
        this._timezoneChangedId = this._clock.connect('notify::timezone',
            () => {
                if (this._animation)
                    this._loadAnimation(this._animation.file);
            });

        let loginManager = LoginManager.getLoginManager();
        this._prepareForSleepId = loginManager.connect('prepare-for-sleep',
            (lm, aboutToSuspend) => {
                if (aboutToSuspend)
                    return;
                this.emit('changed');
            });

        this._settingsChangedSignalId =
            this._settings.connect('changed', this._emitChangedSignal.bind(this));

        this._load();
    }

    destroy() {
        this.background = null;

        this._cancellable.cancel();
        this._removeAnimationTimeout();

        let i;
        let keys = Object.keys(this._fileWatches);
        for (i = 0; i < keys.length; i++) {
            this._cache.disconnect(this._fileWatches[keys[i]]);
        }
        this._fileWatches = null;

        if (this._timezoneChangedId != 0)
            this._clock.disconnect(this._timezoneChangedId);
        this._timezoneChangedId = 0;

        this._clock = null;

        if (this._prepareForSleepId != 0)
            LoginManager.getLoginManager().disconnect(this._prepareForSleepId);
        this._prepareForSleepId = 0;

        if (this._settingsChangedSignalId != 0)
            this._settings.disconnect(this._settingsChangedSignalId);
        this._settingsChangedSignalId = 0;

        if (this._changedIdleId) {
            GLib.source_remove(this._changedIdleId);
            this._changedIdleId = 0;
        }
    }

    _emitChangedSignal() {
        if (this._changedIdleId)
            return;

        this._changedIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this._changedIdleId = 0;
            this.emit('changed');
            return GLib.SOURCE_REMOVE;
        });
    }

    updateResolution() {
        if (this._animation)
            this._refreshAnimation();
    }

    _refreshAnimation() {
        if (!this._animation)
            return;

        this._removeAnimationTimeout();
        this._updateAnimation();
    }

    _setLoaded() {
        if (this.isLoaded)
            return;

        this.isLoaded = true;

        let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this.emit('loaded');
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(id, '[gnome-shell] this.emit');
    }

    _loadPattern() {
        let colorString, res, color, secondColor;

        colorString = this._settings.get_string(PRIMARY_COLOR_KEY);
        [res, color] = Clutter.Color.from_string(colorString);
        colorString = this._settings.get_string(SECONDARY_COLOR_KEY);
        [res, secondColor] = Clutter.Color.from_string(colorString);

        let shadingType = this._settings.get_enum(COLOR_SHADING_TYPE_KEY);

        if (shadingType == GDesktopEnums.BackgroundShading.SOLID)
            this.background.set_color(color);
        else
            this.background.set_gradient(shadingType, color, secondColor);
    }

    _watchFile(file) {
        let key = file.hash();
        if (this._fileWatches[key])
            return;

        this._cache.monitorFile(file);
        let signalId = this._cache.connect('file-changed',
                                           (cache, changedFile) => {
                                               if (changedFile.equal(file)) {
                                                   let imageCache = Meta.BackgroundImageCache.get_default();
                                                   imageCache.purge(changedFile);
                                                   this._emitChangedSignal();
                                               }
                                           });
        this._fileWatches[key] = signalId;
    }

    _removeAnimationTimeout() {
        if (this._updateAnimationTimeoutId) {
            GLib.source_remove(this._updateAnimationTimeoutId);
            this._updateAnimationTimeoutId = 0;
        }
    }

    _updateAnimation() {
        this._updateAnimationTimeoutId = 0;

        this._animation.update(this._layoutManager.monitors[this._monitorIndex]);
        let files = this._animation.keyFrameFiles;

        let finish = () => {
            this._setLoaded();
            if (files.length > 1) {
                this.background.set_blend(files[0], files[1],
                                          this._animation.transitionProgress,
                                          this._style);
            } else if (files.length > 0) {
                this.background.set_file(files[0], this._style);
            } else {
                this.background.set_file(null, this._style);
            }
            this._queueUpdateAnimation();
        };

        let cache = Meta.BackgroundImageCache.get_default();
        let numPendingImages = files.length;
        for (let i = 0; i < files.length; i++) {
            this._watchFile(files[i]);
            let image = cache.load(files[i]);
            if (image.is_loaded()) {
                numPendingImages--;
                if (numPendingImages == 0)
                    finish();
            } else {
                let id = image.connect('loaded', () => {
                    image.disconnect(id);
                    numPendingImages--;
                    if (numPendingImages == 0)
                        finish();
                });
            }
        }
    }

    _queueUpdateAnimation() {
        if (this._updateAnimationTimeoutId != 0)
            return;

        if (!this._cancellable || this._cancellable.is_cancelled())
            return;

        if (!this._animation.transitionDuration)
            return;

        let nSteps = 255 / ANIMATION_OPACITY_STEP_INCREMENT;
        let timePerStep = (this._animation.transitionDuration * 1000) / nSteps;

        let interval = Math.max(ANIMATION_MIN_WAKEUP_INTERVAL * 1000,
                                timePerStep);

        if (interval > GLib.MAXUINT32)
            return;

        this._updateAnimationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                                                      interval,
                                                      () => {
                                                          this._updateAnimationTimeoutId = 0;
                                                          this._updateAnimation();
                                                          return GLib.SOURCE_REMOVE;
                                                      });
        GLib.Source.set_name_by_id(this._updateAnimationTimeoutId, '[gnome-shell] this._updateAnimation');
    }

    _loadAnimation(file) {
        this._cache.getAnimation({ file: file,
                                   settingsSchema: this._settings.schema_id,
                                   onLoaded: animation => {
                                       this._animation = animation;

                                       if (!this._animation || this._cancellable.is_cancelled()) {
                                           this._setLoaded();
                                           return;
                                       }

                                       this._updateAnimation();
                                       this._watchFile(file);
                                   }
                                 });
    }

    _loadImage(file) {
        this.background.set_file(file, this._style);
        this._watchFile(file);

        let cache = Meta.BackgroundImageCache.get_default();
        let image = cache.load(file);
        if (image.is_loaded())
            this._setLoaded();
        else {
            let id = image.connect('loaded', () => {
                this._setLoaded();
                image.disconnect(id);
            });
        }
    }

    _loadFile(file) {
        if (file.get_basename().endsWith('.xml'))
            this._loadAnimation(file);
        else
            this._loadImage(file);
    }

    _load() {
        this._cache = getBackgroundCache();

        this._loadPattern();

        if (!this._file) {
            this._setLoaded();
            return;
        }

        this._loadFile(this._file);
    }
};
Signals.addSignalMethods(Background.prototype);

let _systemBackground;

var SystemBackground = class SystemBackground {
    constructor() {
        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/noise-texture.png');

        if (_systemBackground == null) {
            _systemBackground = new Meta.Background({ meta_display: global.display });
            _systemBackground.set_color(DEFAULT_BACKGROUND_COLOR);
            _systemBackground.set_file(file, GDesktopEnums.BackgroundStyle.WALLPAPER);
        }

        this.actor = new Meta.BackgroundActor({ meta_display: global.display,
                                                monitor: 0,
                                                background: _systemBackground });

        let cache = Meta.BackgroundImageCache.get_default();
        let image = cache.load(file);
        if (image.is_loaded()) {
            image = null;
            let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                this.emit('loaded');
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(id, '[gnome-shell] SystemBackground.loaded');
        } else {
            let id = image.connect('loaded', () => {
                this.emit('loaded');
                image.disconnect(id);
                image = null;
            });
        }
    }
};
Signals.addSignalMethods(SystemBackground.prototype);

var BackgroundSource = class BackgroundSource {
    constructor(layoutManager, settingsSchema) {
        // Allow override the background image setting for performance testing
        this._layoutManager = layoutManager;
        this._overrideImage = GLib.getenv('SHELL_BACKGROUND_IMAGE');
        this._settings = new Gio.Settings({ schema_id: settingsSchema });
        this._backgrounds = [];

        let monitorManager = Meta.MonitorManager.get();
        this._monitorsChangedId =
            monitorManager.connect('monitors-changed',
                                   this._refresh.bind(this));

        global.display.connect('gl-video-memory-purged', () => {
            Meta.Background.refresh_all();
            this._refresh();
        });
    }

    _refresh() {
        for (let monitorIndex in this._backgrounds) {
            let background = this._backgrounds[monitorIndex];

            if (monitorIndex < this._layoutManager.monitors.length) {
                background.updateResolution();
            } else {
                background.disconnect(background._changedId);
                background.destroy();
                delete this._backgrounds[monitorIndex];
            }
        }
    }

    getBackground(monitorIndex) {
        let file = null;
        let style;

        // We don't watch changes to settings here,
        // instead we rely on Background to watch those
        // and emit 'changed' at the right time

        if (this._overrideImage != null) {
            file = Gio.File.new_for_path(this._overrideImage);
            style = GDesktopEnums.BackgroundStyle.ZOOM; // Hardcode
        } else {
            style = this._settings.get_enum(BACKGROUND_STYLE_KEY);
            if (style != GDesktopEnums.BackgroundStyle.NONE) {
                let uri = this._settings.get_string(PICTURE_URI_KEY);
                file = Gio.File.new_for_commandline_arg(uri);
            }
        }

        // Animated backgrounds are (potentially) per-monitor, since
        // they can have variants that depend on the aspect ratio and
        // size of the monitor; for other backgrounds we can use the
        // same background object for all monitors.
        if (file == null || !file.get_basename().endsWith('.xml'))
            monitorIndex = 0;

        if (!(monitorIndex in this._backgrounds)) {
            let background = new Background({
                monitorIndex: monitorIndex,
                layoutManager: this._layoutManager,
                settings: this._settings,
                file: file,
                style: style
            });

            background._changedId = background.connect('changed', () => {
                background.disconnect(background._changedId);
                background.destroy();
                delete this._backgrounds[monitorIndex];
            });

            this._backgrounds[monitorIndex] = background;
        }

        return this._backgrounds[monitorIndex];
    }

    destroy() {
        let monitorManager = Meta.MonitorManager.get();
        monitorManager.disconnect(this._monitorsChangedId);

        for (let monitorIndex in this._backgrounds) {
            let background = this._backgrounds[monitorIndex];
            background.disconnect(background._changedId);
            background.destroy();
        }

        this._backgrounds = null;
    }
};

var Animation = class Animation {
    constructor(params) {
        params = Params.parse(params, { file: null });

        this.file = params.file;
        this.keyFrameFiles = [];
        this.transitionProgress = 0.0;
        this.transitionDuration = 0.0;
        this.loaded = false;
    }

    load(callback) {
        this._show = new GnomeDesktop.BGSlideShow({ filename: this.file.get_path() });

        this._show.load_async(null, (object, result) => {
            this.loaded = true;
            if (callback)
                callback();
        });
    }

    update(monitor) {
        this.keyFrameFiles = [];

        if (!this._show)
            return;

        if (this._show.get_num_slides() < 1)
            return;

        let [progress, duration, isFixed, filename1, filename2] = this._show.get_current_slide(monitor.width, monitor.height);

        this.transitionDuration = duration;
        this.transitionProgress = progress;

        if (filename1)
            this.keyFrameFiles.push(Gio.File.new_for_path(filename1));

        if (filename2)
            this.keyFrameFiles.push(Gio.File.new_for_path(filename2));
    }
};
Signals.addSignalMethods(Animation.prototype);

var BackgroundManager = class BackgroundManager {
    constructor(params) {
        params = Params.parse(params, { container: null,
                                        layoutManager: Main.layoutManager,
                                        monitorIndex: null,
                                        vignette: false,
                                        controlPosition: true,
                                        settingsSchema: BACKGROUND_SCHEMA });

        let cache = getBackgroundCache();
        this._settingsSchema = params.settingsSchema;
        this._backgroundSource = cache.getBackgroundSource(params.layoutManager, params.settingsSchema);

        this._container = params.container;
        this._layoutManager = params.layoutManager;
        this._vignette = params.vignette;
        this._monitorIndex = params.monitorIndex;
        this._controlPosition = params.controlPosition;

        this.backgroundActor = this._createBackgroundActor();
        this._newBackgroundActor = null;
    }

    destroy() {
        let cache = getBackgroundCache();
        cache.releaseBackgroundSource(this._settingsSchema);
        this._backgroundSource = null;

        if (this._newBackgroundActor) {
            this._newBackgroundActor.destroy();
            this._newBackgroundActor = null;
        }

        if (this.backgroundActor) {
            this.backgroundActor.destroy();
            this.backgroundActor = null;
        }
    }

    _swapBackgroundActor() {
        let oldBackgroundActor = this.backgroundActor;
        this.backgroundActor = this._newBackgroundActor;
        this._newBackgroundActor = null;
        this.emit('changed');

        Tweener.addTween(oldBackgroundActor,
                         { opacity: 0,
                           time: FADE_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete() {
                               oldBackgroundActor.destroy();
                           }
                         });
    }

    _updateBackgroundActor() {
        if (this._newBackgroundActor) {
            /* Skip displaying existing background queued for load */
            this._newBackgroundActor.destroy();
            this._newBackgroundActor = null;
        }

        let newBackgroundActor = this._createBackgroundActor();
        newBackgroundActor.vignette_sharpness = this.backgroundActor.vignette_sharpness;
        newBackgroundActor.brightness = this.backgroundActor.brightness;
        newBackgroundActor.visible = this.backgroundActor.visible;

        this._newBackgroundActor = newBackgroundActor;

        let background = newBackgroundActor.background._delegate;

        if (background.isLoaded) {
            this._swapBackgroundActor();
        } else {
            newBackgroundActor.loadedSignalId = background.connect('loaded',
                () => {
                    background.disconnect(newBackgroundActor.loadedSignalId);
                    newBackgroundActor.loadedSignalId = 0;

                    this._swapBackgroundActor();
                });
        }
    }

    _createBackgroundActor() {
        let background = this._backgroundSource.getBackground(this._monitorIndex);
        let backgroundActor = new Meta.BackgroundActor({ meta_display: global.display,
                                                         monitor: this._monitorIndex,
                                                         background: background.background,
                                                         vignette: this._vignette,
                                                         vignette_sharpness: 0.5,
                                                         brightness: 0.5,
                                                       });

        this._container.add_child(backgroundActor);

        if (this._controlPosition) {
            let monitor = this._layoutManager.monitors[this._monitorIndex];
            backgroundActor.set_position(monitor.x, monitor.y);
            backgroundActor.lower_bottom();
        }

        let changeSignalId = background.connect('changed', () => {
            background.disconnect(changeSignalId);
            changeSignalId = null;
            this._updateBackgroundActor();
        });

        backgroundActor.connect('destroy', () => {
            if (changeSignalId)
                background.disconnect(changeSignalId);

            if (backgroundActor.loadedSignalId)
                background.disconnect(backgroundActor.loadedSignalId);
        });

        return backgroundActor;
    }
};
Signals.addSignalMethods(BackgroundManager.prototype);
(uuay)keyboard.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, IBus, Meta, Shell, St } = imports.gi;
const Gettext = imports.gettext;
const Signals = imports.signals;

const IBusManager = imports.misc.ibusManager;
const KeyboardManager = imports.misc.keyboardManager;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const PanelMenu = imports.ui.panelMenu;
const SwitcherPopup = imports.ui.switcherPopup;
const Util = imports.misc.util;

const INPUT_SOURCE_TYPE_XKB = 'xkb';
const INPUT_SOURCE_TYPE_IBUS = 'ibus';

var LayoutMenuItem = class extends PopupMenu.PopupBaseMenuItem {
    constructor(displayName, shortName) {
        super();

        this.label = new St.Label({ text: displayName });
        this.indicator = new St.Label({ text: shortName });
        this.actor.add(this.label, { expand: true });
        this.actor.add(this.indicator);
        this.actor.label_actor = this.label;
    }
};

var InputSource = class {
    constructor(type, id, displayName, shortName, index) {
        this.type = type;
        this.id = id;
        this.displayName = displayName;
        this._shortName = shortName;
        this.index = index;

        this.properties = null;

        this.xkbId = this._getXkbId();
    }

    get shortName() {
        return this._shortName;
    }

    set shortName(v) {
        this._shortName = v;
        this.emit('changed');
    }

    activate(interactive) {
        this.emit('activate', !!interactive);
    }

    _getXkbId() {
        let engineDesc = IBusManager.getIBusManager().getEngineDesc(this.id);
        if (!engineDesc)
            return this.id;

        if (engineDesc.variant && engineDesc.variant.length > 0)
            return engineDesc.layout + '+' + engineDesc.variant;
        else
            return engineDesc.layout;
    }
};
Signals.addSignalMethods(InputSource.prototype);

var InputSourcePopup = GObject.registerClass(
class InputSourcePopup extends SwitcherPopup.SwitcherPopup {
    _init(items, action, actionBackward) {
        super._init(items);

        this._action = action;
        this._actionBackward = actionBackward;

        this._switcherList = new InputSourceSwitcher(this._items);
    }

    _keyPressHandler(keysym, action) {
        if (action == this._action)
            this._select(this._next());
        else if (action == this._actionBackward)
            this._select(this._previous());
        else if (keysym == Clutter.Left)
            this._select(this._previous());
        else if (keysym == Clutter.Right)
            this._select(this._next());
        else
            return Clutter.EVENT_PROPAGATE;

        return Clutter.EVENT_STOP;
    }

    _finish() {
        super._finish();

        this._items[this._selectedIndex].activate(true);
    }
});

var InputSourceSwitcher = GObject.registerClass(
class InputSourceSwitcher extends SwitcherPopup.SwitcherList {
    _init(items) {
        super._init(true);

        for (let i = 0; i < items.length; i++)
            this._addIcon(items[i]);
    }

    _addIcon(item) {
        let box = new St.BoxLayout({ vertical: true });

        let bin = new St.Bin({ style_class: 'input-source-switcher-symbol' });
        let symbol = new St.Label({ text: item.shortName });
        bin.set_child(symbol);
        box.add(bin, { x_fill: false, y_fill: false } );

        let text = new St.Label({ text: item.displayName });
        box.add(text, { x_fill: false });

        this.addItem(box, text);
    }
});

var InputSourceSettings = class {
    constructor() {
        if (new.target === InputSourceSettings)
            throw new TypeError('Cannot instantiate abstract class ' + new.target.name);
    }

    _emitInputSourcesChanged() {
        this.emit('input-sources-changed');
    }

    _emitKeyboardOptionsChanged() {
        this.emit('keyboard-options-changed');
    }

    _emitPerWindowChanged() {
        this.emit('per-window-changed');
    }

    get inputSources() {
        return [];
    }

    get mruSources() {
        return [];
    }

    set mruSources(sourcesList) {
        // do nothing
    }

    get keyboardOptions() {
        return [];
    }

    get perWindow() {
        return false;
    }
};
Signals.addSignalMethods(InputSourceSettings.prototype);

var InputSourceSystemSettings = class extends InputSourceSettings {
    constructor() {
        super();

        this._BUS_NAME = 'org.freedesktop.locale1';
        this._BUS_PATH = '/org/freedesktop/locale1';
        this._BUS_IFACE = 'org.freedesktop.locale1';
        this._BUS_PROPS_IFACE = 'org.freedesktop.DBus.Properties';

        this._layouts = '';
        this._variants = '';
        this._options = '';

        this._reload();

        Gio.DBus.system.signal_subscribe(this._BUS_NAME,
                                         this._BUS_PROPS_IFACE,
                                         'PropertiesChanged',
                                         this._BUS_PATH,
                                         null,
                                         Gio.DBusSignalFlags.NONE,
                                         this._reload.bind(this));
    }

    _reload() {
        Gio.DBus.system.call(this._BUS_NAME,
                             this._BUS_PATH,
                             this._BUS_PROPS_IFACE,
                             'GetAll',
                             new GLib.Variant('(s)', [this._BUS_IFACE]),
                             null, Gio.DBusCallFlags.NONE, -1, null,
                             (conn, result) => {
                                 let props;
                                 try {
                                     props = conn.call_finish(result).deep_unpack()[0];
                                 } catch(e) {
                                     log('Could not get properties from ' + this._BUS_NAME);
                                     return;
                                 }
                                 let layouts = props['X11Layout'].unpack();
                                 let variants = props['X11Variant'].unpack();
                                 let options = props['X11Options'].unpack();

                                 if (layouts != this._layouts ||
                                     variants != this._variants) {
                                     this._layouts = layouts;
                                     this._variants = variants;
                                     this._emitInputSourcesChanged();
                                 }
                                 if (options != this._options) {
                                     this._options = options;
                                     this._emitKeyboardOptionsChanged();
                                 }
                             });
    }

    get inputSources() {
        let sourcesList = [];
        let layouts = this._layouts.split(',');
        let variants = this._variants.split(',');

        for (let i = 0; i < layouts.length && !!layouts[i]; i++) {
            let id = layouts[i];
            if (!!variants[i])
                id += '+' + variants[i];
            sourcesList.push({ type: INPUT_SOURCE_TYPE_XKB, id: id });
        }
        return sourcesList;
    }

    get keyboardOptions() {
        return this._options.split(',');
    }
};

var InputSourceSessionSettings = class extends InputSourceSettings {
    constructor() {
        super();

        this._DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources';
        this._KEY_INPUT_SOURCES = 'sources';
        this._KEY_MRU_SOURCES = 'mru-sources';
        this._KEY_KEYBOARD_OPTIONS = 'xkb-options';
        this._KEY_PER_WINDOW = 'per-window';

        this._settings = new Gio.Settings({ schema_id: this._DESKTOP_INPUT_SOURCES_SCHEMA });
        this._settings.connect('changed::' + this._KEY_INPUT_SOURCES, this._emitInputSourcesChanged.bind(this));
        this._settings.connect('changed::' + this._KEY_KEYBOARD_OPTIONS, this._emitKeyboardOptionsChanged.bind(this));
        this._settings.connect('changed::' + this._KEY_PER_WINDOW, this._emitPerWindowChanged.bind(this));
    }

    _getSourcesList(key) {
        let sourcesList = [];
        let sources = this._settings.get_value(key);
        let nSources = sources.n_children();

        for (let i = 0; i < nSources; i++) {
            let [type, id] = sources.get_child_value(i).deep_unpack();
            sourcesList.push({ type: type, id: id });
        }
        return sourcesList;
    }

    get inputSources() {
        return this._getSourcesList(this._KEY_INPUT_SOURCES);
    }

    get mruSources() {
        return this._getSourcesList(this._KEY_MRU_SOURCES);
    }

    set mruSources(sourcesList) {
        let sources = GLib.Variant.new('a(ss)', sourcesList);
        this._settings.set_value(this._KEY_MRU_SOURCES, sources);
    }

    get keyboardOptions() {
        return this._settings.get_strv(this._KEY_KEYBOARD_OPTIONS);
    }

    get perWindow() {
        return this._settings.get_boolean(this._KEY_PER_WINDOW);
    }
};

var InputSourceManager = class {
    constructor() {
        // All valid input sources currently in the gsettings
        // KEY_INPUT_SOURCES list indexed by their index there
        this._inputSources = {};
        // All valid input sources currently in the gsettings
        // KEY_INPUT_SOURCES list of type INPUT_SOURCE_TYPE_IBUS
        // indexed by the IBus ID
        this._ibusSources = {};

        this._currentSource = null;

        // All valid input sources currently in the gsettings
        // KEY_INPUT_SOURCES list ordered by most recently used
        this._mruSources = [];
        this._mruSourcesBackup = null;
        this._keybindingAction =
            Main.wm.addKeybinding('switch-input-source',
                                  new Gio.Settings({ schema_id: "org.gnome.desktop.wm.keybindings" }),
                                  Meta.KeyBindingFlags.NONE,
                                  Shell.ActionMode.ALL,
                                  this._switchInputSource.bind(this));
        this._keybindingActionBackward =
            Main.wm.addKeybinding('switch-input-source-backward',
                                  new Gio.Settings({ schema_id: "org.gnome.desktop.wm.keybindings" }),
                                  Meta.KeyBindingFlags.IS_REVERSED,
                                  Shell.ActionMode.ALL,
                                  this._switchInputSource.bind(this));
        if (Main.sessionMode.isGreeter)
            this._settings = new InputSourceSystemSettings();
        else
            this._settings = new InputSourceSessionSettings();
        this._settings.connect('input-sources-changed', this._inputSourcesChanged.bind(this));
        this._settings.connect('keyboard-options-changed', this._keyboardOptionsChanged.bind(this));

        this._xkbInfo = KeyboardManager.getXkbInfo();
        this._keyboardManager = KeyboardManager.getKeyboardManager();

        this._ibusReady = false;
        this._ibusManager = IBusManager.getIBusManager();
        this._ibusManager.connect('ready', this._ibusReadyCallback.bind(this));
        this._ibusManager.connect('properties-registered', this._ibusPropertiesRegistered.bind(this));
        this._ibusManager.connect('property-updated', this._ibusPropertyUpdated.bind(this));
        this._ibusManager.connect('set-content-type', this._ibusSetContentType.bind(this));

        global.display.connect('modifiers-accelerator-activated', this._modifiersSwitcher.bind(this));

        this._sourcesPerWindow = false;
        this._focusWindowNotifyId = 0;
        this._overviewShowingId = 0;
        this._overviewHiddenId = 0;
        this._settings.connect('per-window-changed', this._sourcesPerWindowChanged.bind(this));
        this._sourcesPerWindowChanged();
        this._disableIBus = false;
        this._reloading = false;
    }

    reload() {
        this._reloading = true;
        this._keyboardManager.setKeyboardOptions(this._settings.keyboardOptions);
        this._inputSourcesChanged();
        this._reloading = false;
    }

    _ibusReadyCallback(im, ready) {
        if (this._ibusReady == ready)
            return;

        this._ibusReady = ready;
        this._mruSources = [];
        this._inputSourcesChanged();
    }

    _modifiersSwitcher() {
        let sourceIndexes = Object.keys(this._inputSources);
        if (sourceIndexes.length == 0) {
            KeyboardManager.releaseKeyboard();
            return true;
        }

        let is = this._currentSource;
        if (!is)
            is = this._inputSources[sourceIndexes[0]];

        let nextIndex = is.index + 1;
        if (nextIndex > sourceIndexes[sourceIndexes.length - 1])
            nextIndex = 0;

        while (!(is = this._inputSources[nextIndex]))
            nextIndex += 1;

        is.activate(true);
        return true;
    }

    _switchInputSource(display, window, binding) {
        if (this._mruSources.length < 2)
            return;

        // HACK: Fall back on simple input source switching since we
        // can't show a popup switcher while a GrabHelper grab is in
        // effect without considerable work to consolidate the usage
        // of pushModal/popModal and grabHelper. See
        // https://bugzilla.gnome.org/show_bug.cgi?id=695143 .
        if (Main.actionMode == Shell.ActionMode.POPUP) {
            this._modifiersSwitcher();
            return;
        }

        let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward);
        if (!popup.show(binding.is_reversed(), binding.get_name(), binding.get_mask()))
            popup.fadeAndDestroy();
    }

    _keyboardOptionsChanged() {
        this._keyboardManager.setKeyboardOptions(this._settings.keyboardOptions);
        this._keyboardManager.reapply();
    }

    _updateMruSettings() {
        // If IBus is not ready we don't have a full picture of all
        // the available sources, so don't update the setting
        if (!this._ibusReady)
            return;

        // If IBus is temporarily disabled, don't update the setting
        if (this._disableIBus)
            return;

        let sourcesList = [];
        for (let i = 0; i < this._mruSources.length; ++i) {
            let source = this._mruSources[i];
            sourcesList.push([source.type, source.id]);
        }

        this._settings.mruSources = sourcesList;
    }

    _currentInputSourceChanged(newSource) {
        let oldSource;
        [oldSource, this._currentSource] = [this._currentSource, newSource];

        this.emit('current-source-changed', oldSource);

        for (let i = 1; i < this._mruSources.length; ++i)
            if (this._mruSources[i] == newSource) {
                let currentSource = this._mruSources.splice(i, 1);
                this._mruSources = currentSource.concat(this._mruSources);
                break;
            }

        this._changePerWindowSource();
    }

    activateInputSource(is, interactive) {
        // The focus changes during holdKeyboard/releaseKeyboard may trick
        // the client into hiding UI containing the currently focused entry.
        // So holdKeyboard/releaseKeyboard are not called when
        // 'set-content-type' signal is received.
        // E.g. Focusing on a password entry in a popup in Xorg Firefox
        // will emit 'set-content-type' signal.
        // https://gitlab.gnome.org/GNOME/gnome-shell/issues/391
        if (!this._reloading)
            KeyboardManager.holdKeyboard();
        this._keyboardManager.apply(is.xkbId);

        // All the "xkb:..." IBus engines simply "echo" back symbols,
        // despite their naming implying differently, so we always set
        // one in order for XIM applications to work given that we set
        // XMODIFIERS=@im=ibus in the first place so that they can
        // work without restarting when/if the user adds an IBus input
        // source.
        let engine;
        if (is.type == INPUT_SOURCE_TYPE_IBUS)
            engine = is.id;
        else
            engine = 'xkb:us::eng';

        if (!this._reloading)
            this._ibusManager.setEngine(engine, KeyboardManager.releaseKeyboard);
        else
            this._ibusManager.setEngine(engine);
        this._currentInputSourceChanged(is);

        if (interactive)
            this._updateMruSettings();
    }

    _updateMruSources() {
        let sourcesList = [];
        for (let i in this._inputSources)
            sourcesList.push(this._inputSources[i]);

        this._keyboardManager.setUserLayouts(sourcesList.map(x => x.xkbId));

        if (!this._disableIBus && this._mruSourcesBackup) {
            this._mruSources = this._mruSourcesBackup;
            this._mruSourcesBackup = null;
        }

        // Initialize from settings when we have no MRU sources list
        if (this._mruSources.length == 0) {
            let mruSettings = this._settings.mruSources;
            for (let i = 0; i < mruSettings.length; i++) {
                let mruSettingSource = mruSettings[i];
                let mruSource = null;

                for (let j = 0; j < sourcesList.length; j++) {
                    let source = sourcesList[j];
                    if (source.type == mruSettingSource.type &&
                        source.id == mruSettingSource.id) {
                        mruSource = source;
                        break;
                    }
                }

                if (mruSource)
                    this._mruSources.push(mruSource);
            }
        }

        let mruSources = [];
        for (let i = 0; i < this._mruSources.length; i++) {
            for (let j = 0; j < sourcesList.length; j++)
                if (this._mruSources[i].type == sourcesList[j].type &&
                    this._mruSources[i].id == sourcesList[j].id) {
                    mruSources = mruSources.concat(sourcesList.splice(j, 1));
                    break;
                }
        }
        this._mruSources = mruSources.concat(sourcesList);
    }

    _inputSourcesChanged() {
        let sources = this._settings.inputSources;
        let nSources = sources.length;

        this._currentSource = null;
        this._inputSources = {};
        this._ibusSources = {};

        let infosList = [];
        for (let i = 0; i < nSources; i++) {
            let displayName;
            let shortName;
            let type = sources[i].type;
            let id = sources[i].id;
            let exists = false;

            if (type == INPUT_SOURCE_TYPE_XKB) {
                [exists, displayName, shortName, , ] =
                    this._xkbInfo.get_layout_info(id);
            } else if (type == INPUT_SOURCE_TYPE_IBUS) {
                if (this._disableIBus)
                    continue;
                let engineDesc = this._ibusManager.getEngineDesc(id);
                if (engineDesc) {
                    let language = IBus.get_language_name(engineDesc.get_language());
                    let longName = engineDesc.get_longname();
                    let textdomain = engineDesc.get_textdomain();
                    if (textdomain != '')
                        longName = Gettext.dgettext(textdomain, longName);
                    exists = true;
                    displayName = '%s (%s)'.format(language, longName);
                    shortName = this._makeEngineShortName(engineDesc);
                }
            }

            if (exists)
                infosList.push({ type: type, id: id, displayName: displayName, shortName: shortName });
        }

        if (infosList.length == 0) {
            let type = INPUT_SOURCE_TYPE_XKB;
            let id = KeyboardManager.DEFAULT_LAYOUT;
            let [ , displayName, shortName, , ] = this._xkbInfo.get_layout_info(id);
            infosList.push({ type: type, id: id, displayName: displayName, shortName: shortName });
        }

        let inputSourcesByShortName = {};
        for (let i = 0; i < infosList.length; i++) {
            let is = new InputSource(infosList[i].type,
                                     infosList[i].id,
                                     infosList[i].displayName,
                                     infosList[i].shortName,
                                     i);
            is.connect('activate', this.activateInputSource.bind(this));

            if (!(is.shortName in inputSourcesByShortName))
                inputSourcesByShortName[is.shortName] = [];
            inputSourcesByShortName[is.shortName].push(is);

            this._inputSources[is.index] = is;

            if (is.type == INPUT_SOURCE_TYPE_IBUS)
                this._ibusSources[is.id] = is;
        }

        for (let i in this._inputSources) {
            let is = this._inputSources[i];
            if (inputSourcesByShortName[is.shortName].length > 1) {
                let sub = inputSourcesByShortName[is.shortName].indexOf(is) + 1;
                is.shortName += String.fromCharCode(0x2080 + sub);
            }
        }

        this.emit('sources-changed');

        this._updateMruSources();

        if (this._mruSources.length > 0)
            this._mruSources[0].activate(false);

        // All ibus engines are preloaded here to reduce the launching time
        // when users switch the input sources.
        this._ibusManager.preloadEngines(Object.keys(this._ibusSources));
    }

    _makeEngineShortName(engineDesc) {
        let symbol = engineDesc.get_symbol();
        if (symbol && symbol[0])
            return symbol;

        let langCode = engineDesc.get_language().split('_', 1)[0];
        if (langCode.length == 2 || langCode.length == 3)
            return langCode.toLowerCase();

        return String.fromCharCode(0x2328); // keyboard glyph
    }

    _ibusPropertiesRegistered(im, engineName, props) {
        let source = this._ibusSources[engineName];
        if (!source)
            return;

        source.properties = props;

        if (source == this._currentSource)
            this.emit('current-source-changed', null);
    }

    _ibusPropertyUpdated(im, engineName, prop) {
        let source = this._ibusSources[engineName];
        if (!source)
            return;

        if (this._updateSubProperty(source.properties, prop) &&
            source == this._currentSource)
            this.emit('current-source-changed', null);
    }

    _updateSubProperty(props, prop) {
        if (!props)
            return false;

        let p;
        for (let i = 0; (p = props.get(i)) != null; ++i) {
            if (p.get_key() == prop.get_key() && p.get_prop_type() == prop.get_prop_type()) {
                p.update(prop);
                return true;
            } else if (p.get_prop_type() == IBus.PropType.MENU) {
                if (this._updateSubProperty(p.get_sub_props(), prop))
                    return true;
            }
        }
        return false;
    }

    _ibusSetContentType(im, purpose, hints) {
        if (purpose == IBus.InputPurpose.PASSWORD) {
            if (Object.keys(this._inputSources).length == Object.keys(this._ibusSources).length)
                return;

            if (this._disableIBus)
                return;
            this._disableIBus = true;
            this._mruSourcesBackup = this._mruSources.slice();
        } else {
            if (!this._disableIBus)
                return;
            this._disableIBus = false;
        }
        this.reload();
    }

    _getNewInputSource(current) {
        let sourceIndexes = Object.keys(this._inputSources);
        if (sourceIndexes.length == 0)
            return null;

        if (current) {
            for (let i in this._inputSources) {
                let is = this._inputSources[i];
                if (is.type == current.type &&
                    is.id == current.id)
                    return is;
            }
        }

        return this._inputSources[sourceIndexes[0]];
    }

    _getCurrentWindow() {
        if (Main.overview.visible)
            return Main.overview;
        else
            return global.display.focus_window;
    }

    _setPerWindowInputSource() {
        let window = this._getCurrentWindow();
        if (!window)
            return;

        if (window._inputSources != this._inputSources) {
            window._inputSources = this._inputSources;
            window._currentSource = this._getNewInputSource(window._currentSource);
        }

        if (window._currentSource)
            window._currentSource.activate(false);
    }

    _sourcesPerWindowChanged() {
        this._sourcesPerWindow = this._settings.perWindow;

        if (this._sourcesPerWindow && this._focusWindowNotifyId == 0) {
            this._focusWindowNotifyId = global.display.connect('notify::focus-window',
                                                               this._setPerWindowInputSource.bind(this));
            this._overviewShowingId = Main.overview.connect('showing',
                                                            this._setPerWindowInputSource.bind(this));
            this._overviewHiddenId = Main.overview.connect('hidden',
                                                           this._setPerWindowInputSource.bind(this));
        } else if (!this._sourcesPerWindow && this._focusWindowNotifyId != 0) {
            global.display.disconnect(this._focusWindowNotifyId);
            this._focusWindowNotifyId = 0;
            Main.overview.disconnect(this._overviewShowingId);
            this._overviewShowingId = 0;
            Main.overview.disconnect(this._overviewHiddenId);
            this._overviewHiddenId = 0;

            let windows = global.get_window_actors().map(w => w.meta_window);
            for (let i = 0; i < windows.length; ++i) {
                delete windows[i]._inputSources;
                delete windows[i]._currentSource;
            }
            delete Main.overview._inputSources;
            delete Main.overview._currentSource;
        }
    }

    _changePerWindowSource() {
        if (!this._sourcesPerWindow)
            return;

        let window = this._getCurrentWindow();
        if (!window)
            return;

        window._inputSources = this._inputSources;
        window._currentSource = this._currentSource;
    }

    get currentSource() {
        return this._currentSource;
    }

    get inputSources() {
        return this._inputSources;
    }
};
Signals.addSignalMethods(InputSourceManager.prototype);

let _inputSourceManager = null;

function getInputSourceManager() {
    if (_inputSourceManager == null)
        _inputSourceManager = new InputSourceManager();
    return _inputSourceManager;
}

var InputSourceIndicatorContainer = GObject.registerClass(
class InputSourceIndicatorContainer extends St.Widget {

    vfunc_get_preferred_width(forHeight) {
        // Here, and in vfunc_get_preferred_height, we need to query
        // for the height of all children, but we ignore the results
        // for those we don't actually display.
        return this.get_children().reduce((maxWidth, child) => {
            let width = child.get_preferred_width(forHeight);
            return [Math.max(maxWidth[0], width[0]),
                    Math.max(maxWidth[1], width[1])];
        }, [0, 0]);
    }

    vfunc_get_preferred_height(forWidth) {
        return this.get_children().reduce((maxHeight, child) => {
            let height = child.get_preferred_height(forWidth);
            return [Math.max(maxHeight[0], height[0]),
                    Math.max(maxHeight[1], height[1])];
        }, [0, 0]);
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        // translate box to (0, 0)
        box.x2 -= box.x1;
        box.x1 = 0;
        box.y2 -= box.y1;
        box.y1 = 0;

        this.get_children().forEach(c => {
            c.allocate_align_fill(box, 0.5, 0.5, false, false, flags);
        });
    }
});

var InputSourceIndicator = GObject.registerClass(
class InputSourceIndicator extends PanelMenu.Button {
    _init() {
        super._init(0.0, _("Keyboard"));

        this.connect('destroy', this._onDestroy.bind(this));

        this._menuItems = {};
        this._indicatorLabels = {};

        this._container = new InputSourceIndicatorContainer();

        this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
        this._hbox.add_child(this._container);
        this._hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));

        this.actor.add_child(this._hbox);

        this._propSeparator = new PopupMenu.PopupSeparatorMenuItem();
        this.menu.addMenuItem(this._propSeparator);
        this._propSection = new PopupMenu.PopupMenuSection();
        this.menu.addMenuItem(this._propSection);
        this._propSection.actor.hide();

        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this._showLayoutItem = this.menu.addAction(_("Show Keyboard Layout"), this._showLayout.bind(this));

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();

        this._inputSourceManager = getInputSourceManager();
        this._inputSourceManagerSourcesChangedId =
            this._inputSourceManager.connect('sources-changed', this._sourcesChanged.bind(this));
        this._inputSourceManagerCurrentSourceChangedId =
            this._inputSourceManager.connect('current-source-changed', this._currentSourceChanged.bind(this));
        this._inputSourceManager.reload();
    }

    _onDestroy() {
        if (this._inputSourceManager) {
            this._inputSourceManager.disconnect(this._inputSourceManagerSourcesChangedId);
            this._inputSourceManager.disconnect(this._inputSourceManagerCurrentSourceChangedId);
            this._inputSourceManager = null;
        }
    }

    _sessionUpdated() {
        // re-using "allowSettings" for the keyboard layout is a bit shady,
        // but at least for now it is used as "allow popping up windows
        // from shell menus"; we can always add a separate sessionMode
        // option if need arises.
        this._showLayoutItem.actor.visible = Main.sessionMode.allowSettings;
    }

    _sourcesChanged() {
        for (let i in this._menuItems)
            this._menuItems[i].destroy();
        for (let i in this._indicatorLabels)
            this._indicatorLabels[i].destroy();

        this._menuItems = {};
        this._indicatorLabels = {};

        let menuIndex = 0;
        for (let i in this._inputSourceManager.inputSources) {
            let is = this._inputSourceManager.inputSources[i];

            let menuItem = new LayoutMenuItem(is.displayName, is.shortName);
            menuItem.connect('activate', () => { is.activate(true); });

            let indicatorLabel = new St.Label({ text: is.shortName,
                                                visible: false });

            this._menuItems[i] = menuItem;
            this._indicatorLabels[i] = indicatorLabel;
            is.connect('changed', () => {
                menuItem.indicator.set_text(is.shortName);
                indicatorLabel.set_text(is.shortName);
            });

            this.menu.addMenuItem(menuItem, menuIndex++);
            this._container.add_actor(indicatorLabel);
        }
    }

    _currentSourceChanged(manager, oldSource) {
        let nVisibleSources = Object.keys(this._inputSourceManager.inputSources).length;
        let newSource = this._inputSourceManager.currentSource;

        if (oldSource) {
            this._menuItems[oldSource.index].setOrnament(PopupMenu.Ornament.NONE);
            this._indicatorLabels[oldSource.index].hide();
        }

        if (!newSource || (nVisibleSources < 2 && !newSource.properties)) {
            // This source index might be invalid if we weren't able
            // to build a menu item for it, so we hide ourselves since
            // we can't fix it here. *shrug*

            // We also hide if we have only one visible source unless
            // it's an IBus source with properties.
            this.menu.close();
            this.actor.hide();
            return;
        }

        this.actor.show();

        this._buildPropSection(newSource.properties);

        this._menuItems[newSource.index].setOrnament(PopupMenu.Ornament.DOT);
        this._indicatorLabels[newSource.index].show();
    }

    _buildPropSection(properties) {
        this._propSeparator.actor.hide();
        this._propSection.actor.hide();
        this._propSection.removeAll();

        this._buildPropSubMenu(this._propSection, properties);

        if (!this._propSection.isEmpty()) {
            this._propSection.actor.show();
            this._propSeparator.actor.show();
        }
    }

    _buildPropSubMenu(menu, props) {
        if (!props)
            return;

        let ibusManager = IBusManager.getIBusManager();
        let radioGroup = [];
        let p;
        for (let i = 0; (p = props.get(i)) != null; ++i) {
            let prop = p;

            if (!prop.get_visible())
                continue;

            if (prop.get_key() == 'InputMode') {
                let text;
                if (prop.get_symbol)
                    text = prop.get_symbol().get_text();
                else
                    text = prop.get_label().get_text();

                let currentSource = this._inputSourceManager.currentSource;
                if (currentSource) {
                    let indicatorLabel = this._indicatorLabels[currentSource.index];
                    if (text && text.length > 0 && text.length < 3)
                        indicatorLabel.set_text(text);
                }
            }

            let item;
            let type = prop.get_prop_type();
            switch (type) {
            case IBus.PropType.MENU:
                item = new PopupMenu.PopupSubMenuMenuItem(prop.get_label().get_text());
                this._buildPropSubMenu(item.menu, prop.get_sub_props());
                break;

            case IBus.PropType.RADIO:
                item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
                item.prop = prop;
                radioGroup.push(item);
                item.radioGroup = radioGroup;
                item.setOrnament(prop.get_state() == IBus.PropState.CHECKED ?
                                 PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE);
                item.connect('activate', () => {
                    if (item.prop.get_state() == IBus.PropState.CHECKED)
                        return;

                    let group = item.radioGroup;
                    for (let i = 0; i < group.length; ++i) {
                        if (group[i] == item) {
                            item.setOrnament(PopupMenu.Ornament.DOT);
                            item.prop.set_state(IBus.PropState.CHECKED);
                            ibusManager.activateProperty(item.prop.get_key(),
                                                         IBus.PropState.CHECKED);
                        } else {
                            group[i].setOrnament(PopupMenu.Ornament.NONE);
                            group[i].prop.set_state(IBus.PropState.UNCHECKED);
                            ibusManager.activateProperty(group[i].prop.get_key(),
                                                         IBus.PropState.UNCHECKED);
                        }
                    }
                });
                break;

            case IBus.PropType.TOGGLE:
                item = new PopupMenu.PopupSwitchMenuItem(prop.get_label().get_text(), prop.get_state() == IBus.PropState.CHECKED);
                item.prop = prop;
                item.connect('toggled', () => {
                    if (item.state) {
                        item.prop.set_state(IBus.PropState.CHECKED);
                        ibusManager.activateProperty(item.prop.get_key(),
                                                     IBus.PropState.CHECKED);
                    } else {
                        item.prop.set_state(IBus.PropState.UNCHECKED);
                        ibusManager.activateProperty(item.prop.get_key(),
                                                     IBus.PropState.UNCHECKED);
                    }
                });
                break;

            case IBus.PropType.NORMAL:
                item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
                item.prop = prop;
                item.connect('activate', () => {
                    ibusManager.activateProperty(item.prop.get_key(),
                                                 item.prop.get_state());
                });
                break;

            case IBus.PropType.SEPARATOR:
                item = new PopupMenu.PopupSeparatorMenuItem();
                break;

            default:
                log ('IBus property %s has invalid type %d'.format(prop.get_key(), type));
                continue;
            }

            item.setSensitive(prop.get_sensitive());
            menu.addMenuItem(item);
        }
    }

    _showLayout() {
        Main.overview.hide();

        let source = this._inputSourceManager.currentSource;
        let xkbLayout = '';
        let xkbVariant = '';

        if (source.type == INPUT_SOURCE_TYPE_XKB) {
            [, , , xkbLayout, xkbVariant] = KeyboardManager.getXkbInfo().get_layout_info(source.id);
        } else if (source.type == INPUT_SOURCE_TYPE_IBUS) {
            let engineDesc = IBusManager.getIBusManager().getEngineDesc(source.id);
            if (engineDesc) {
                xkbLayout = engineDesc.get_layout();
                xkbVariant = engineDesc.get_layout_variant();
            }
        }

        if (!xkbLayout || xkbLayout.length == 0)
            return;

        let description = xkbLayout;
        if (xkbVariant.length > 0)
            description = description + '\t' + xkbVariant;

        Util.spawn(['gkbd-keyboard-display', '-l', description]);
    }
});
(uuay)nightLight.js�
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const { loadInterfaceXML } = imports.misc.fileUtils;

const BUS_NAME = 'org.gnome.SettingsDaemon.Color';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Color';

const ColorInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Color');
const ColorProxy = Gio.DBusProxy.makeProxyWrapper(ColorInterface);

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'night-light-symbolic';
        this._proxy = new ColorProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
                                     (proxy, error) => {
                                         if (error) {
                                             log(error.message);
                                             return;
                                         }
                                         this._proxy.connect('g-properties-changed',
                                                             this._sync.bind(this));
                                         this._sync();
                                     });

        this._item = new PopupMenu.PopupSubMenuMenuItem("", true);
        this._item.icon.icon_name = 'night-light-symbolic';
        this._disableItem = this._item.menu.addAction('', () => {
            this._proxy.DisabledUntilTomorrow = !this._proxy.DisabledUntilTomorrow;
        });
        this._item.menu.addAction(_("Turn Off"), () => {
            let settings = new Gio.Settings({ schema_id: 'org.gnome.settings-daemon.plugins.color' });
            settings.set_boolean('night-light-enabled', false);
        });
        this._item.menu.addSettingsAction(_("Display Settings"), 'gnome-display-panel.desktop');
        this.menu.addMenuItem(this._item);

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
        this._sync();
    }

    _sessionUpdated() {
        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        this.menu.setSensitive(sensitive);
    }

    _sync() {
        let visible = this._proxy.NightLightActive;
        let disabled = this._proxy.DisabledUntilTomorrow;

        this._item.label.text = disabled ? _("Night Light Disabled")
                                         : _("Night Light On");
        this._disableItem.label.text = disabled ? _("Resume")
                                                : _("Disable Until Tomorrow");
        this._item.actor.visible = this._indicator.visible = visible;
    }
};
(uuay)closeDialog.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, Shell } = imports.gi;

const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var FROZEN_WINDOW_BRIGHTNESS = -0.3
var DIALOG_TRANSITION_TIME = 0.15
var ALIVE_TIMEOUT = 5000;

var CloseDialog = GObject.registerClass({
    Implements: [ Meta.CloseDialog ],
    Properties: {
        'window': GObject.ParamSpec.override('window', Meta.CloseDialog)
    },
}, class CloseDialog extends GObject.Object {
    _init(window) {
        super._init();
        this._window = window;
        this._dialog = null;
        this._tracked = undefined;
        this._timeoutId = 0;
        this._windowFocusChangedId = 0;
        this._keyFocusChangedId = 0;
    }

    get window() {
        return this._window;
    }

    set window(window) {
        this._window = window;
    }

    _createDialogContent() {
        let tracker = Shell.WindowTracker.get_default();
        let windowApp = tracker.get_window_app(this._window);

        /* Translators: %s is an application name */
        let title = _("“%s” is not responding.").format(windowApp.get_name());
        let subtitle = _("You may choose to wait a short while for it to " +
                         "continue or force the application to quit entirely.");
        let icon = new Gio.ThemedIcon({ name: 'dialog-warning-symbolic' });
        return new Dialog.MessageDialogContent({ icon, title, subtitle });
    }

    _initDialog() {
        if (this._dialog)
            return;

        let windowActor = this._window.get_compositor_private();
        this._dialog = new Dialog.Dialog(windowActor, 'close-dialog');
        this._dialog.width = windowActor.width;
        this._dialog.height = windowActor.height;

        this._dialog.addContent(this._createDialogContent());
        this._dialog.addButton({ label:   _('Force Quit'),
                                 action:  this._onClose.bind(this),
                                 default: true });
        this._dialog.addButton({ label:  _('Wait'),
                                 action: this._onWait.bind(this),
                                 key:    Clutter.Escape });

        global.focus_manager.add_group(this._dialog);
    }

    _addWindowEffect() {
        // We set the effect on the surface actor, so the dialog itself
        // (which is a child of the MetaWindowActor) does not get the
        // effect applied itself.
        let windowActor = this._window.get_compositor_private();
        let surfaceActor = windowActor.get_first_child();
        let effect = new Clutter.BrightnessContrastEffect();
        effect.set_brightness(FROZEN_WINDOW_BRIGHTNESS);
        surfaceActor.add_effect_with_name("gnome-shell-frozen-window", effect);
    }

    _removeWindowEffect() {
        let windowActor = this._window.get_compositor_private();
        let surfaceActor = windowActor.get_first_child();
        surfaceActor.remove_effect_by_name("gnome-shell-frozen-window");
    }

    _onWait() {
        this.response(Meta.CloseDialogResponse.WAIT);
    }

    _onClose() {
        this.response(Meta.CloseDialogResponse.FORCE_CLOSE);
    }

    _onFocusChanged() {
        if (Meta.is_wayland_compositor())
            return;

        let focusWindow = global.display.focus_window;
        let keyFocus = global.stage.key_focus;

        let shouldTrack;
        if (focusWindow != null)
            shouldTrack = focusWindow == this._window;
        else
            shouldTrack = keyFocus && this._dialog.contains(keyFocus);

        if (this._tracked === shouldTrack)
            return;

        if (shouldTrack)
            Main.layoutManager.trackChrome(this._dialog,
                                           { affectsInputRegion: true });
        else
            Main.layoutManager.untrackChrome(this._dialog);

        // The buttons are broken when they aren't added to the input region,
        // so disable them properly in that case
        this._dialog.buttonLayout.get_children().forEach(b => {
            b.reactive = shouldTrack;
        });

        this._tracked = shouldTrack;
    }

    vfunc_show() {
        if (this._dialog != null)
            return;

        Meta.disable_unredirect_for_display(global.display);

        this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ALIVE_TIMEOUT,
            () => {
                this._window.check_alive(global.display.get_current_time_roundtrip());
                return GLib.SOURCE_CONTINUE;
            });

        this._windowFocusChangedId =
            global.display.connect('notify::focus-window',
                                   this._onFocusChanged.bind(this));

        this._keyFocusChangedId =
            global.stage.connect('notify::key-focus',
                                 this._onFocusChanged.bind(this));

        this._addWindowEffect();
        this._initDialog();

        this._dialog.scale_y = 0;
        this._dialog.set_pivot_point(0.5, 0.5);

        Tweener.addTween(this._dialog,
                         { scale_y: 1,
                           transition: 'linear',
                           time: DIALOG_TRANSITION_TIME,
                           onComplete: this._onFocusChanged.bind(this)
                         });
    }

    vfunc_hide() {
        if (this._dialog == null)
            return;

        Meta.enable_unredirect_for_display(global.display);

        GLib.source_remove(this._timeoutId);
        this._timeoutId = 0;

        global.display.disconnect(this._windowFocusChangedId)
        this._windowFocusChangedId = 0;

        global.stage.disconnect(this._keyFocusChangedId);
        this._keyFocusChangedId = 0;

        let dialog = this._dialog;
        this._dialog = null;
        this._removeWindowEffect();

        Tweener.addTween(dialog,
                         { scale_y: 0,
                           transition: 'linear',
                           time: DIALOG_TRANSITION_TIME,
                           onComplete: () => {
                               dialog.destroy();
                           }
                         });
    }

    vfunc_focus() {
        if (this._dialog)
            this._dialog.grab_key_focus();
    }
});
(uuay)oVirt.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;
const Signals = imports.signals;

const OVirtCredentialsIface = `
<node>
<interface name="org.ovirt.vdsm.Credentials">
<signal name="UserAuthenticated">
    <arg type="s" name="token"/>
</signal>
</interface>
</node>`;

const OVirtCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(OVirtCredentialsIface);

let _oVirtCredentialsManager = null;

function OVirtCredentials() {
    var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system,
                                   g_interface_name: OVirtCredentialsInfo.name,
                                   g_interface_info: OVirtCredentialsInfo,
                                   g_name: 'org.ovirt.vdsm.Credentials',
                                   g_object_path: '/org/ovirt/vdsm/Credentials',
                                   g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });
    self.init(null);
    return self;
}

var OVirtCredentialsManager = class {
    constructor() {
        this._token = null;

        this._credentials = new OVirtCredentials();
        this._credentials.connectSignal('UserAuthenticated',
                                        this._onUserAuthenticated.bind(this));
    }

    _onUserAuthenticated(proxy, sender, [token]) {
        this._token = token;
        this.emit('user-authenticated', token);
    }

    hasToken() {
        return this._token != null;
    }

    getToken() {
        return this._token;
    }

    resetToken() {
        this._token = null;
    }
};
Signals.addSignalMethods(OVirtCredentialsManager.prototype);

function getOVirtCredentialsManager() {
    if (!_oVirtCredentialsManager)
        _oVirtCredentialsManager = new OVirtCredentialsManager();

    return _oVirtCredentialsManager;
}
(uuay)workspaceThumbnail.js>�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const Background = imports.ui.background;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const Workspace = imports.ui.workspace;
const WorkspacesView = imports.ui.workspacesView;

// The maximum size of a thumbnail is 1/10 the width and height of the screen
let MAX_THUMBNAIL_SCALE = 1/10.;

var RESCALE_ANIMATION_TIME = 0.2;
var SLIDE_ANIMATION_TIME = 0.2;

// When we create workspaces by dragging, we add a "cut" into the top and
// bottom of each workspace so that the user doesn't have to hit the
// placeholder exactly.
var WORKSPACE_CUT_SIZE = 10;

var WORKSPACE_KEEP_ALIVE_TIME = 100;

var MUTTER_SCHEMA = 'org.gnome.mutter';

/* A layout manager that requests size only for primary_actor, but then allocates
   all using a fixed layout */
var PrimaryActorLayout = GObject.registerClass(
class PrimaryActorLayout extends Clutter.FixedLayout {
    _init(primaryActor) {
        super._init();

        this.primaryActor = primaryActor;
    }

    vfunc_get_preferred_width(container, forHeight) {
        return this.primaryActor.get_preferred_width(forHeight);
    }

    vfunc_get_preferred_height(container, forWidth) {
        return this.primaryActor.get_preferred_height(forWidth);
    }
});

var WindowClone = class {
    constructor(realWindow) {
        this.clone = new Clutter.Clone({ source: realWindow });

        /* Can't use a Shell.GenericContainer because of DND and reparenting... */
        this.actor = new Clutter.Actor({ layout_manager: new PrimaryActorLayout(this.clone),
                                         reactive: true });
        this.actor._delegate = this;
        this.actor.add_child(this.clone);
        this.realWindow = realWindow;
        this.metaWindow = realWindow.meta_window;

        this.clone._updateId = this.realWindow.connect('notify::position',
                                                       this._onPositionChanged.bind(this));
        this.clone._destroyId = this.realWindow.connect('destroy', () => {
            // First destroy the clone and then destroy everything
            // This will ensure that we never see it in the _disconnectSignals loop
            this.clone.destroy();
            this.destroy();
        });
        this._onPositionChanged();

        this.actor.connect('button-release-event',
                           this._onButtonRelease.bind(this));
        this.actor.connect('touch-event',
                           this._onTouchEvent.bind(this));

        this.actor.connect('destroy', this._onDestroy.bind(this));

        this._draggable = DND.makeDraggable(this.actor,
                                            { restoreOnSuccess: true,
                                              dragActorMaxSize: Workspace.WINDOW_DND_SIZE,
                                              dragActorOpacity: Workspace.DRAGGING_WINDOW_OPACITY });
        this._draggable.connect('drag-begin', this._onDragBegin.bind(this));
        this._draggable.connect('drag-cancelled', this._onDragCancelled.bind(this));
        this._draggable.connect('drag-end', this._onDragEnd.bind(this));
        this.inDrag = false;

        let iter = win => {
            let actor = win.get_compositor_private();

            if (!actor)
                return false;
            if (!win.is_attached_dialog())
                return false;

            this._doAddAttachedDialog(win, actor);
            win.foreach_transient(iter);

            return true;
        };
        this.metaWindow.foreach_transient(iter);
    }

    // Find the actor just below us, respecting reparenting done
    // by DND code
    getActualStackAbove() {
        if (this._stackAbove == null)
            return null;

        if (this.inDrag) {
            if (this._stackAbove._delegate)
                return this._stackAbove._delegate.getActualStackAbove();
            else
                return null;
        } else {
            return this._stackAbove;
        }
    }

    setStackAbove(actor) {
        this._stackAbove = actor;

        // Don't apply the new stacking now, it will be applied
        // when dragging ends and window are stacked again
        if (actor.inDrag)
            return;

        let actualAbove = this.getActualStackAbove();
        if (actualAbove == null)
            this.actor.lower_bottom();
        else
            this.actor.raise(actualAbove);
    }

    destroy() {
        this.actor.destroy();
    }

    addAttachedDialog(win) {
        this._doAddAttachedDialog(win, win.get_compositor_private());
    }

    _doAddAttachedDialog(metaDialog, realDialog) {
        let clone = new Clutter.Clone({ source: realDialog });
        this._updateDialogPosition(realDialog, clone);

        clone._updateId = realDialog.connect('notify::position', dialog => {
            this._updateDialogPosition(dialog, clone);
        });
        clone._destroyId = realDialog.connect('destroy', () => {
            clone.destroy();
        });
        this.actor.add_child(clone);
    }

    _updateDialogPosition(realDialog, cloneDialog) {
        let metaDialog = realDialog.meta_window;
        let dialogRect = metaDialog.get_frame_rect();
        let rect = this.metaWindow.get_frame_rect();

        cloneDialog.set_position(dialogRect.x - rect.x, dialogRect.y - rect.y);
    }

    _onPositionChanged() {
        this.actor.set_position(this.realWindow.x, this.realWindow.y);
    }

    _disconnectSignals() {
        this.actor.get_children().forEach(child => {
            let realWindow = child.source;

            realWindow.disconnect(child._updateId);
            realWindow.disconnect(child._destroyId);
        });
    }

    _onDestroy() {
        this._disconnectSignals();

        this.actor._delegate = null;

        if (this.inDrag) {
            this.emit('drag-end');
            this.inDrag = false;
        }

        this.disconnectAll();
    }

    _onButtonRelease(actor, event) {
        this.emit('selected', event.get_time());

        return Clutter.EVENT_STOP;
    }

    _onTouchEvent(actor, event) {
        if (event.type() != Clutter.EventType.TOUCH_END ||
            !global.display.is_pointer_emulating_sequence(event.get_event_sequence()))
            return Clutter.EVENT_PROPAGATE;

        this.emit('selected', event.get_time());
        return Clutter.EVENT_STOP;
    }

    _onDragBegin(draggable, time) {
        this.inDrag = true;
        this.emit('drag-begin');
    }

    _onDragCancelled(draggable, time) {
        this.emit('drag-cancelled');
    }

    _onDragEnd(draggable, time, snapback) {
        this.inDrag = false;

        // We may not have a parent if DnD completed successfully, in
        // which case our clone will shortly be destroyed and replaced
        // with a new one on the target workspace.
        if (this.actor.get_parent() != null) {
            if (this._stackAbove == null)
                this.actor.lower_bottom();
            else
                this.actor.raise(this._stackAbove);
        }


        this.emit('drag-end');
    }
};
Signals.addSignalMethods(WindowClone.prototype);


var ThumbnailState = {
    NEW   :         0,
    ANIMATING_IN :  1,
    NORMAL:         2,
    REMOVING :      3,
    ANIMATING_OUT : 4,
    ANIMATED_OUT :  5,
    COLLAPSING :    6,
    DESTROYED :     7
};

/**
 * @metaWorkspace: a #Meta.Workspace
 */
var WorkspaceThumbnail = class {
    constructor(metaWorkspace) {
        this.metaWorkspace = metaWorkspace;
        this.monitorIndex = Main.layoutManager.primaryIndex;

        this._removed = false;

        this.actor = new St.Widget({ clip_to_allocation: true,
                                     style_class: 'workspace-thumbnail' });
        this.actor._delegate = this;

        this._contents = new Clutter.Actor();
        this.actor.add_child(this._contents);

        this.actor.connect('destroy', this._onDestroy.bind(this));

        this._createBackground();

        let workArea = Main.layoutManager.getWorkAreaForMonitor(this.monitorIndex);
        this.setPorthole(workArea.x, workArea.y, workArea.width, workArea.height);

        let windows = global.get_window_actors().filter(actor => {
            let win = actor.meta_window;
            return win.located_on_workspace(metaWorkspace);
        });

        // Create clones for windows that should be visible in the Overview
        this._windows = [];
        this._allWindows = [];
        this._minimizedChangedIds = [];
        for (let i = 0; i < windows.length; i++) {
            let minimizedChangedId =
                windows[i].meta_window.connect('notify::minimized',
                                               this._updateMinimized.bind(this));
            this._allWindows.push(windows[i].meta_window);
            this._minimizedChangedIds.push(minimizedChangedId);

            if (this._isMyWindow(windows[i]) && this._isOverviewWindow(windows[i])) {
                this._addWindowClone(windows[i]);
            }
        }

        // Track window changes
        this._windowAddedId = this.metaWorkspace.connect('window-added',
                                                          this._windowAdded.bind(this));
        this._windowRemovedId = this.metaWorkspace.connect('window-removed',
                                                           this._windowRemoved.bind(this));
        this._windowEnteredMonitorId = global.display.connect('window-entered-monitor',
                                                              this._windowEnteredMonitor.bind(this));
        this._windowLeftMonitorId = global.display.connect('window-left-monitor',
                                                           this._windowLeftMonitor.bind(this));

        this.state = ThumbnailState.NORMAL;
        this._slidePosition = 0; // Fully slid in
        this._collapseFraction = 0; // Not collapsed
    }

    _createBackground() {
        this._bgManager = new Background.BackgroundManager({ monitorIndex: Main.layoutManager.primaryIndex,
                                                             container: this._contents,
                                                             vignette: false });
    }

    setPorthole(x, y, width, height) {
        this.actor.set_size(width, height);
        this._contents.set_position(-x, -y);
    }

    _lookupIndex(metaWindow) {
        return this._windows.findIndex(w => w.metaWindow == metaWindow);
    }

    syncStacking(stackIndices) {
        this._windows.sort((a, b) => {
            let indexA = stackIndices[a.metaWindow.get_stable_sequence()];
            let indexB = stackIndices[b.metaWindow.get_stable_sequence()];
            return indexA - indexB;
        });

        for (let i = 0; i < this._windows.length; i++) {
            let clone = this._windows[i];
            let metaWindow = clone.metaWindow;
            if (i == 0) {
                clone.setStackAbove(this._bgManager.backgroundActor);
            } else {
                let previousClone = this._windows[i - 1];
                clone.setStackAbove(previousClone.actor);
            }
        }
    }

    set slidePosition(slidePosition) {
        this._slidePosition = slidePosition;
        this.actor.queue_relayout();
    }

    get slidePosition() {
        return this._slidePosition;
    }

    set collapseFraction(collapseFraction) {
        this._collapseFraction = collapseFraction;
        this.actor.queue_relayout();
    }

    get collapseFraction() {
        return this._collapseFraction;
    }

    _doRemoveWindow(metaWin) {
        let clone = this._removeWindowClone(metaWin);
        if (clone)
            clone.destroy();
    }

    _doAddWindow(metaWin) {
        if (this._removed)
            return;

        let win = metaWin.get_compositor_private();

        if (!win) {
            // Newly-created windows are added to a workspace before
            // the compositor finds out about them...
            let id = Mainloop.idle_add(() => {
                if (!this._removed &&
                    metaWin.get_compositor_private() &&
                    metaWin.get_workspace() == this.metaWorkspace)
                    this._doAddWindow(metaWin);
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(id, '[gnome-shell] this._doAddWindow');
            return;
        }

        if (this._allWindows.indexOf(metaWin) == -1) {
            let minimizedChangedId = metaWin.connect('notify::minimized',
                                                     this._updateMinimized.bind(this));
            this._allWindows.push(metaWin);
            this._minimizedChangedIds.push(minimizedChangedId);
        }

        // We might have the window in our list already if it was on all workspaces and
        // now was moved to this workspace
        if (this._lookupIndex(metaWin) != -1)
            return;

        if (!this._isMyWindow(win))
            return;

        if (this._isOverviewWindow(win)) {
            this._addWindowClone(win);
        } else if (metaWin.is_attached_dialog()) {
            let parent = metaWin.get_transient_for();
            while (parent.is_attached_dialog())
                parent = parent.get_transient_for();

            let idx = this._lookupIndex(parent);
            if (idx < 0) {
                // parent was not created yet, it will take care
                // of the dialog when created
                return;
            }

            let clone = this._windows[idx];
            clone.addAttachedDialog(metaWin);
        }
    }

    _windowAdded(metaWorkspace, metaWin) {
        this._doAddWindow(metaWin);
    }

    _windowRemoved(metaWorkspace, metaWin) {
        let index = this._allWindows.indexOf(metaWin);
        if (index != -1) {
            metaWin.disconnect(this._minimizedChangedIds[index]);
            this._allWindows.splice(index, 1);
            this._minimizedChangedIds.splice(index, 1);
        }

        this._doRemoveWindow(metaWin);
    }

    _windowEnteredMonitor(metaDisplay, monitorIndex, metaWin) {
        if (monitorIndex == this.monitorIndex) {
            this._doAddWindow(metaWin);
        }
    }

    _windowLeftMonitor(metaDisplay, monitorIndex, metaWin) {
        if (monitorIndex == this.monitorIndex) {
            this._doRemoveWindow(metaWin);
        }
    }

    _updateMinimized(metaWin) {
        if (metaWin.minimized)
            this._doRemoveWindow(metaWin);
        else
            this._doAddWindow(metaWin);
    }

    destroy() {
        if (this.actor)
          this.actor.destroy();
    }

    workspaceRemoved() {
        if (this._removed)
            return;

        this._removed = true;

        this.metaWorkspace.disconnect(this._windowAddedId);
        this.metaWorkspace.disconnect(this._windowRemovedId);
        global.display.disconnect(this._windowEnteredMonitorId);
        global.display.disconnect(this._windowLeftMonitorId);

        for (let i = 0; i < this._allWindows.length; i++)
            this._allWindows[i].disconnect(this._minimizedChangedIds[i]);
    }

    _onDestroy(actor) {
        this.workspaceRemoved();

        if (this._bgManager) {
          this._bgManager.destroy();
          this._bgManager = null;
        }

        this._windows = [];
        this.actor = null;
    }

    // Tests if @actor belongs to this workspace and monitor
    _isMyWindow(actor) {
        let win = actor.meta_window;
        return win.located_on_workspace(this.metaWorkspace) &&
            (win.get_monitor() == this.monitorIndex);
    }

    // Tests if @win should be shown in the Overview
    _isOverviewWindow(win) {
        return !win.get_meta_window().skip_taskbar &&
               win.get_meta_window().showing_on_its_workspace();
    }

    // Create a clone of a (non-desktop) window and add it to the window list
    _addWindowClone(win) {
        let clone = new WindowClone(win);

        clone.connect('selected', (clone, time) => {
            this.activate(time);
        });
        clone.connect('drag-begin', () => {
            Main.overview.beginWindowDrag(clone.metaWindow);
        });
        clone.connect('drag-cancelled', () => {
            Main.overview.cancelledWindowDrag(clone.metaWindow);
        });
        clone.connect('drag-end', () => {
            Main.overview.endWindowDrag(clone.metaWindow);
        });
        clone.actor.connect('destroy', () => {
            this._removeWindowClone(clone.metaWindow);
        });
        this._contents.add_actor(clone.actor);

        if (this._windows.length == 0)
            clone.setStackAbove(this._bgManager.backgroundActor);
        else
            clone.setStackAbove(this._windows[this._windows.length - 1].actor);

        this._windows.push(clone);

        return clone;
    }

    _removeWindowClone(metaWin) {
        // find the position of the window in our list
        let index = this._lookupIndex(metaWin);

        if (index == -1)
            return null;

        return this._windows.splice(index, 1).pop();
    }

    activate(time) {
        if (this.state > ThumbnailState.NORMAL)
            return;

        // a click on the already current workspace should go back to the main view
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        if (this.metaWorkspace == activeWorkspace)
            Main.overview.hide();
        else
            this.metaWorkspace.activate(time);
    }

    // Draggable target interface used only by ThumbnailsBox
    handleDragOverInternal(source, time) {
        if (source == Main.xdndHandler) {
            this.metaWorkspace.activate(time);
            return DND.DragMotionResult.CONTINUE;
        }

        if (this.state > ThumbnailState.NORMAL)
            return DND.DragMotionResult.CONTINUE;

        if (source.realWindow && !this._isMyWindow(source.realWindow))
            return DND.DragMotionResult.MOVE_DROP;
        if (source.shellWorkspaceLaunch)
            return DND.DragMotionResult.COPY_DROP;

        return DND.DragMotionResult.CONTINUE;
    }

    acceptDropInternal(source, time) {
        if (this.state > ThumbnailState.NORMAL)
            return false;

        if (source.realWindow) {
            let win = source.realWindow;
            if (this._isMyWindow(win))
                return false;

            let metaWindow = win.get_meta_window();

            // We need to move the window before changing the workspace, because
            // the move itself could cause a workspace change if the window enters
            // the primary monitor
            if (metaWindow.get_monitor() != this.monitorIndex)
                metaWindow.move_to_monitor(this.monitorIndex);

            metaWindow.change_workspace_by_index(this.metaWorkspace.index(), false);
            return true;
        } else if (source.shellWorkspaceLaunch) {
            source.shellWorkspaceLaunch({ workspace: this.metaWorkspace ? this.metaWorkspace.index() : -1,
                                          timestamp: time });
            return true;
        }

        return false;
    }
};
Signals.addSignalMethods(WorkspaceThumbnail.prototype);


var ThumbnailsBox = GObject.registerClass(
class ThumbnailsBox extends St.Widget {
    _init() {
        super._init({ reactive: true,
                      style_class: 'workspace-thumbnails',
                      request_mode: Clutter.RequestMode.WIDTH_FOR_HEIGHT });

        this.actor = this;
        this.actor._delegate = this;

        let indicator = new St.Bin({ style_class: 'workspace-thumbnail-indicator' });

        // We don't want the indicator to affect drag-and-drop
        Shell.util_set_hidden_from_pick(indicator, true);

        this._indicator = indicator;
        this.add_actor(indicator);

        // The porthole is the part of the screen we're showing in the thumbnails
        this._porthole = { width: global.stage.width, height: global.stage.height,
                           x: global.stage.x, y: global.stage.y };

        this._dropWorkspace = -1;
        this._dropPlaceholderPos = -1;
        this._dropPlaceholder = new St.Bin({ style_class: 'placeholder' });
        this.add_actor(this._dropPlaceholder);
        this._spliceIndex = -1;

        this._targetScale = 0;
        this._scale = 0;
        this._pendingScaleUpdate = false;
        this._stateUpdateQueued = false;
        this._animatingIndicator = false;
        this._indicatorY = 0; // only used when _animatingIndicator is true

        this._stateCounts = {};
        for (let key in ThumbnailState)
            this._stateCounts[ThumbnailState[key]] = 0;

        this._thumbnails = [];

        this.connect('button-press-event', () => Clutter.EVENT_STOP);
        this.connect('button-release-event', this._onButtonRelease.bind(this));
        this.connect('touch-event', this._onTouchEvent.bind(this));

        Main.overview.connect('showing',
                              this._createThumbnails.bind(this));
        Main.overview.connect('hidden',
                              this._destroyThumbnails.bind(this));

        Main.overview.connect('item-drag-begin',
                              this._onDragBegin.bind(this));
        Main.overview.connect('item-drag-end',
                              this._onDragEnd.bind(this));
        Main.overview.connect('item-drag-cancelled',
                              this._onDragCancelled.bind(this));
        Main.overview.connect('window-drag-begin',
                              this._onDragBegin.bind(this));
        Main.overview.connect('window-drag-end',
                              this._onDragEnd.bind(this));
        Main.overview.connect('window-drag-cancelled',
                              this._onDragCancelled.bind(this));

        this._settings = new Gio.Settings({ schema_id: MUTTER_SCHEMA });
        this._settings.connect('changed::dynamic-workspaces',
            this._updateSwitcherVisibility.bind(this));

        Main.layoutManager.connect('monitors-changed', () => {
            this._destroyThumbnails();
            if (Main.overview.visible)
                this._createThumbnails();
        });

        global.display.connect('workareas-changed',
                               this._updatePorthole.bind(this));

        this._switchWorkspaceNotifyId = 0;
        this._nWorkspacesNotifyId = 0;
        this._syncStackingId = 0;
        this._workareasChangedId = 0;
    }

    _updateSwitcherVisibility() {
        let workspaceManager = global.workspace_manager;

        this.visible =
            this._settings.get_boolean('dynamic-workspaces') ||
                workspaceManager.n_workspaces > 1;
    }

    _activateThumbnailAtPoint(stageX, stageY, time) {
        let [r, x, y] = this.transform_stage_point(stageX, stageY);

        for (let i = 0; i < this._thumbnails.length; i++) {
            let thumbnail = this._thumbnails[i]
            let [w, h] = thumbnail.actor.get_transformed_size();
            if (y >= thumbnail.actor.y && y <= thumbnail.actor.y + h) {
                thumbnail.activate(time);
                break;
            }
        }
    }

    _onButtonRelease(actor, event) {
        let [stageX, stageY] = event.get_coords();
        this._activateThumbnailAtPoint(stageX, stageY, event.get_time());
        return Clutter.EVENT_STOP;
    }

    _onTouchEvent(actor, event) {
        if (event.type() == Clutter.EventType.TOUCH_END &&
            global.display.is_pointer_emulating_sequence(event.get_event_sequence())) {
            let [stageX, stageY] = event.get_coords();
            this._activateThumbnailAtPoint(stageX, stageY, event.get_time());
        }

        return Clutter.EVENT_STOP;
    }

    _onDragBegin() {
        this._dragCancelled = false;
        this._dragMonitor = {
            dragMotion: this._onDragMotion.bind(this)
        };
        DND.addDragMonitor(this._dragMonitor);
    }

    _onDragEnd() {
        if (this._dragCancelled)
            return;

        this._endDrag();
    }

    _onDragCancelled() {
        this._dragCancelled = true;
        this._endDrag();
    }

    _endDrag() {
        this._clearDragPlaceholder();
        DND.removeDragMonitor(this._dragMonitor);
    }

    _onDragMotion(dragEvent) {
        if (!this.contains(dragEvent.targetActor))
            this._onLeave();
        return DND.DragMotionResult.CONTINUE;
    }

    _onLeave() {
        this._clearDragPlaceholder();
    }

    _clearDragPlaceholder() {
        if (this._dropPlaceholderPos == -1)
            return;

        this._dropPlaceholderPos = -1;
        this.queue_relayout();
    }

    // Draggable target interface
    handleDragOver(source, actor, x, y, time) {
        if (!source.realWindow && !source.shellWorkspaceLaunch && source != Main.xdndHandler)
            return DND.DragMotionResult.CONTINUE;

        let canCreateWorkspaces = Meta.prefs_get_dynamic_workspaces();
        let spacing = this.get_theme_node().get_length('spacing');

        this._dropWorkspace = -1;
        let placeholderPos = -1;
        let targetBase;
        if (this._dropPlaceholderPos == 0)
            targetBase = this._dropPlaceholder.y;
        else
            targetBase = this._thumbnails[0].actor.y;
        let targetTop = targetBase - spacing - WORKSPACE_CUT_SIZE;
        let length = this._thumbnails.length;
        for (let i = 0; i < length; i ++) {
            // Allow the reorder target to have a 10px "cut" into
            // each side of the thumbnail, to make dragging onto the
            // placeholder easier
            let [w, h] = this._thumbnails[i].actor.get_transformed_size();
            let targetBottom = targetBase + WORKSPACE_CUT_SIZE;
            let nextTargetBase = targetBase + h + spacing;
            let nextTargetTop =  nextTargetBase - spacing - ((i == length - 1) ? 0: WORKSPACE_CUT_SIZE);

            // Expand the target to include the placeholder, if it exists.
            if (i == this._dropPlaceholderPos)
                targetBottom += this._dropPlaceholder.get_height();

            if (y > targetTop && y <= targetBottom && source != Main.xdndHandler && canCreateWorkspaces) {
                placeholderPos = i;
                break;
            } else if (y > targetBottom && y <= nextTargetTop) {
                this._dropWorkspace = i;
                break
            }

            targetBase = nextTargetBase;
            targetTop = nextTargetTop;
        }

        if (this._dropPlaceholderPos != placeholderPos) {
            this._dropPlaceholderPos = placeholderPos;
            this.queue_relayout();
        }

        if (this._dropWorkspace != -1)
            return this._thumbnails[this._dropWorkspace].handleDragOverInternal(source, time);
        else if (this._dropPlaceholderPos != -1)
            return source.realWindow ? DND.DragMotionResult.MOVE_DROP : DND.DragMotionResult.COPY_DROP;
        else
            return DND.DragMotionResult.CONTINUE;
    }

    acceptDrop(source, actor, x, y, time) {
        if (this._dropWorkspace != -1) {
            return this._thumbnails[this._dropWorkspace].acceptDropInternal(source, time);
        } else if (this._dropPlaceholderPos != -1) {
            if (!source.realWindow && !source.shellWorkspaceLaunch)
                return false;

            let isWindow = !!source.realWindow;

            let newWorkspaceIndex;
            [newWorkspaceIndex, this._dropPlaceholderPos] = [this._dropPlaceholderPos, -1];
            this._spliceIndex = newWorkspaceIndex;

            Main.wm.insertWorkspace(newWorkspaceIndex);

            if (isWindow) {
                // Move the window to our monitor first if necessary.
                let thumbMonitor = this._thumbnails[newWorkspaceIndex].monitorIndex;
                if (source.metaWindow.get_monitor() != thumbMonitor)
                    source.metaWindow.move_to_monitor(thumbMonitor);
                source.metaWindow.change_workspace_by_index(newWorkspaceIndex, true);
            } else if (source.shellWorkspaceLaunch) {
                source.shellWorkspaceLaunch({ workspace: newWorkspaceIndex,
                                              timestamp: time });
                // This new workspace will be automatically removed if the application fails
                // to open its first window within some time, as tracked by Shell.WindowTracker.
                // Here, we only add a very brief timeout to avoid the _immediate_ removal of the
                // workspace while we wait for the startup sequence to load.
                let workspaceManager = global.workspace_manager;
                Main.wm.keepWorkspaceAlive(workspaceManager.get_workspace_by_index(newWorkspaceIndex),
                                           WORKSPACE_KEEP_ALIVE_TIME);
            }

            // Start the animation on the workspace (which is actually
            // an old one which just became empty)
            let thumbnail = this._thumbnails[newWorkspaceIndex];
            this._setThumbnailState(thumbnail, ThumbnailState.NEW);
            thumbnail.slidePosition = 1;

            this._queueUpdateStates();

            return true;
        } else {
            return false;
        }
    }

    _createThumbnails() {
        let workspaceManager = global.workspace_manager;

        this._switchWorkspaceNotifyId =
            global.window_manager.connect('switch-workspace',
                                          this._activeWorkspaceChanged.bind(this));
        this._nWorkspacesNotifyId =
            workspaceManager.connect('notify::n-workspaces',
                                     this._workspacesChanged.bind(this));
        this._syncStackingId =
            Main.overview.connect('windows-restacked',
                                  this._syncStacking.bind(this));

        this._targetScale = 0;
        this._scale = 0;
        this._pendingScaleUpdate = false;
        this._stateUpdateQueued = false;

        this._stateCounts = {};
        for (let key in ThumbnailState)
            this._stateCounts[ThumbnailState[key]] = 0;

        this.addThumbnails(0, workspaceManager.n_workspaces);

        this._updateSwitcherVisibility();
    }

    _destroyThumbnails() {
        if (this._thumbnails.length == 0)
            return;

        if (this._switchWorkspaceNotifyId > 0) {
            global.window_manager.disconnect(this._switchWorkspaceNotifyId);
            this._switchWorkspaceNotifyId = 0;
        }
        if (this._nWorkspacesNotifyId > 0) {
            let workspaceManager = global.workspace_manager;
            workspaceManager.disconnect(this._nWorkspacesNotifyId);
            this._nWorkspacesNotifyId = 0;
        }

        if (this._syncStackingId > 0) {
            Main.overview.disconnect(this._syncStackingId);
            this._syncStackingId = 0;
        }

        for (let w = 0; w < this._thumbnails.length; w++)
            this._thumbnails[w].destroy();
        this._thumbnails = [];
    }

    _workspacesChanged() {
        let validThumbnails =
            this._thumbnails.filter(t => t.state <= ThumbnailState.NORMAL);
        let workspaceManager = global.workspace_manager;
        let oldNumWorkspaces = validThumbnails.length;
        let newNumWorkspaces = workspaceManager.n_workspaces;
        let active = workspaceManager.get_active_workspace_index();

        if (newNumWorkspaces > oldNumWorkspaces) {
            this.addThumbnails(oldNumWorkspaces, newNumWorkspaces - oldNumWorkspaces);
        } else {
            let removedIndex;
            let removedNum = oldNumWorkspaces - newNumWorkspaces;
            for (let w = 0; w < oldNumWorkspaces; w++) {
                let metaWorkspace = workspaceManager.get_workspace_by_index(w);
                if (this._thumbnails[w].metaWorkspace != metaWorkspace) {
                    removedIndex = w;
                    break;
                }
            }

            this.removeThumbnails(removedIndex, removedNum);
        }

        this._updateSwitcherVisibility();
    }

    addThumbnails(start, count) {
        let workspaceManager = global.workspace_manager;

        for (let k = start; k < start + count; k++) {
            let metaWorkspace = workspaceManager.get_workspace_by_index(k);
            let thumbnail = new WorkspaceThumbnail(metaWorkspace);
            thumbnail.setPorthole(this._porthole.x, this._porthole.y,
                                  this._porthole.width, this._porthole.height);
            this._thumbnails.push(thumbnail);
            this.add_actor(thumbnail.actor);

            if (start > 0 && this._spliceIndex == -1) {
                // not the initial fill, and not splicing via DND
                thumbnail.state = ThumbnailState.NEW;
                thumbnail.slidePosition = 1; // start slid out
                this._haveNewThumbnails = true;
            } else {
                thumbnail.state = ThumbnailState.NORMAL;
            }

            this._stateCounts[thumbnail.state]++;
        }

        this._queueUpdateStates();

        // The thumbnails indicator actually needs to be on top of the thumbnails
        this._indicator.raise_top();

        // Clear the splice index, we got the message
        this._spliceIndex = -1;
    }

    removeThumbnails(start, count) {
        let currentPos = 0;
        for (let k = 0; k < this._thumbnails.length; k++) {
            let thumbnail = this._thumbnails[k];

            if (thumbnail.state > ThumbnailState.NORMAL)
                continue;

            if (currentPos >= start && currentPos < start + count) {
                thumbnail.workspaceRemoved();
                this._setThumbnailState(thumbnail, ThumbnailState.REMOVING);
            }

            currentPos++;
        }

        this._queueUpdateStates();
    }

    _syncStacking(overview, stackIndices) {
        for (let i = 0; i < this._thumbnails.length; i++)
            this._thumbnails[i].syncStacking(stackIndices);
    }

    set scale(scale) {
        this._scale = scale;
        this.queue_relayout();
    }

    get scale() {
        return this._scale;
    }

    set indicatorY(indicatorY) {
        this._indicatorY = indicatorY;
        this.queue_relayout();
    }

    get indicatorY() {
        return this._indicatorY;
    }

    _setThumbnailState(thumbnail, state) {
        this._stateCounts[thumbnail.state]--;
        thumbnail.state = state;
        this._stateCounts[thumbnail.state]++;
    }

    _iterateStateThumbnails(state, callback) {
        if (this._stateCounts[state] == 0)
            return;

        for (let i = 0; i < this._thumbnails.length; i++) {
            if (this._thumbnails[i].state == state)
                callback.call(this, this._thumbnails[i]);
        }
    }

    _tweenScale() {
        Tweener.addTween(this,
                         { scale: this._targetScale,
                           time: RESCALE_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._queueUpdateStates,
                           onCompleteScope: this });
    }

    _updateStates() {
        this._stateUpdateQueued = false;

        // If we are animating the indicator, wait
        if (this._animatingIndicator)
            return;

        // Then slide out any thumbnails that have been destroyed
        this._iterateStateThumbnails(ThumbnailState.REMOVING, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.ANIMATING_OUT);

            Tweener.addTween(thumbnail,
                             { slidePosition: 1,
                               time: SLIDE_ANIMATION_TIME,
                               transition: 'linear',
                               onComplete: () => {
                                   this._setThumbnailState(thumbnail, ThumbnailState.ANIMATED_OUT);
                                   this._queueUpdateStates();
                               }
                             });
        });

        // As long as things are sliding out, don't proceed
        if (this._stateCounts[ThumbnailState.ANIMATING_OUT] > 0)
            return;

        // Once that's complete, we can start scaling to the new size and collapse any removed thumbnails
        this._iterateStateThumbnails(ThumbnailState.ANIMATED_OUT, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.COLLAPSING);
            Tweener.addTween(thumbnail,
                             { collapseFraction: 1,
                               time: RESCALE_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this._stateCounts[thumbnail.state]--;
                                   thumbnail.state = ThumbnailState.DESTROYED;

                                   let index = this._thumbnails.indexOf(thumbnail);
                                   this._thumbnails.splice(index, 1);
                                   thumbnail.destroy();

                                   this._queueUpdateStates();
                               }
                             });
        });

        if (this._pendingScaleUpdate) {
            this._tweenScale();
            this._pendingScaleUpdate = false;
        }

        // Wait until that's done
        if (this._scale != this._targetScale || this._stateCounts[ThumbnailState.COLLAPSING] > 0)
            return;

        // And then slide in any new thumbnails
        this._iterateStateThumbnails(ThumbnailState.NEW, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.ANIMATING_IN);
            Tweener.addTween(thumbnail,
                             { slidePosition: 0,
                               time: SLIDE_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this._setThumbnailState(thumbnail, ThumbnailState.NORMAL);
                               }
                             });
        });
    }

    _queueUpdateStates() {
        if (this._stateUpdateQueued)
            return;

        Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
                       this._updateStates.bind(this));

        this._stateUpdateQueued = true;
    }

    vfunc_get_preferred_height(forWidth) {
        // Note that for getPreferredWidth/Height we cheat a bit and skip propagating
        // the size request to our children because we know how big they are and know
        // that the actors aren't depending on the virtual functions being called.
        let workspaceManager = global.workspace_manager;
        let themeNode = this.get_theme_node();

        let spacing = themeNode.get_length('spacing');
        let nWorkspaces = workspaceManager.n_workspaces;
        let totalSpacing = (nWorkspaces - 1) * spacing;

        let naturalHeight = totalSpacing + nWorkspaces * this._porthole.height * MAX_THUMBNAIL_SCALE;

        return themeNode.adjust_preferred_height(totalSpacing, naturalHeight);
    }

    vfunc_get_preferred_width(forHeight) {
        let workspaceManager = global.workspace_manager;
        let themeNode = this.get_theme_node();

        forHeight = themeNode.adjust_for_height(forHeight);

        let spacing = themeNode.get_length('spacing');
        let nWorkspaces = workspaceManager.n_workspaces;
        let totalSpacing = (nWorkspaces - 1) * spacing;

        let avail = forHeight - totalSpacing;

        let scale = (avail / nWorkspaces) / this._porthole.height;
        scale = Math.min(scale, MAX_THUMBNAIL_SCALE);

        let width = Math.round(this._porthole.width * scale);

        return themeNode.adjust_preferred_width(width, width);
    }

    _updatePorthole() {
        if (!Main.layoutManager.primaryMonitor)
            this._porthole = { width: global.stage.width, height: global.stage.height,
                               x: global.stage.x, y: global.stage.y };
        else
            this._porthole = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);

        this.queue_relayout();
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let rtl = (Clutter.get_default_text_direction () == Clutter.TextDirection.RTL);

        if (this._thumbnails.length == 0) // not visible
            return;

        let workspaceManager = global.workspace_manager;
        let themeNode = this.get_theme_node();

        box = themeNode.get_content_box(box);

        let portholeWidth = this._porthole.width;
        let portholeHeight = this._porthole.height;
        let spacing = themeNode.get_length('spacing');

        // Compute the scale we'll need once everything is updated
        let nWorkspaces = workspaceManager.n_workspaces;
        let totalSpacing = (nWorkspaces - 1) * spacing;
        let avail = (box.y2 - box.y1) - totalSpacing;

        let newScale = (avail / nWorkspaces) / portholeHeight;
        newScale = Math.min(newScale, MAX_THUMBNAIL_SCALE);

        if (newScale != this._targetScale) {
            if (this._targetScale > 0) {
                // We don't do the tween immediately because we need to observe the ordering
                // in queueUpdateStates - if workspaces have been removed we need to slide them
                // out as the first thing.
                this._targetScale = newScale;
                this._pendingScaleUpdate = true;
            } else {
                this._targetScale = this._scale = newScale;
            }

            this._queueUpdateStates();
        }

        let thumbnailHeight = portholeHeight * this._scale;
        let thumbnailWidth = Math.round(portholeWidth * this._scale);
        let roundedHScale = thumbnailWidth / portholeWidth;

        let slideOffset; // X offset when thumbnail is fully slid offscreen
        if (rtl)
            slideOffset = - (thumbnailWidth + themeNode.get_padding(St.Side.LEFT));
        else
            slideOffset = thumbnailWidth + themeNode.get_padding(St.Side.RIGHT);

        let indicatorY1 = this._indicatorY;
        let indicatorY2;
        // when not animating, the workspace position overrides this._indicatorY
        let activeWorkspace = workspaceManager.get_active_workspace();
        let indicatorWorkspace = !this._animatingIndicator ? activeWorkspace : null;
        let indicatorThemeNode = this._indicator.get_theme_node();

        let indicatorTopFullBorder = indicatorThemeNode.get_padding(St.Side.TOP) + indicatorThemeNode.get_border_width(St.Side.TOP);
        let indicatorBottomFullBorder = indicatorThemeNode.get_padding(St.Side.BOTTOM) + indicatorThemeNode.get_border_width(St.Side.BOTTOM);
        let indicatorLeftFullBorder = indicatorThemeNode.get_padding(St.Side.LEFT) + indicatorThemeNode.get_border_width(St.Side.LEFT);
        let indicatorRightFullBorder = indicatorThemeNode.get_padding(St.Side.RIGHT) + indicatorThemeNode.get_border_width(St.Side.RIGHT);

        let y = box.y1;

        if (this._dropPlaceholderPos == -1) {
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this._dropPlaceholder.hide();
            });
        }

        let childBox = new Clutter.ActorBox();

        for (let i = 0; i < this._thumbnails.length; i++) {
            let thumbnail = this._thumbnails[i];

            if (i > 0)
                y += spacing - Math.round(thumbnail.collapseFraction * spacing);

            let x1, x2;
            if (rtl) {
                x1 = box.x1 + slideOffset * thumbnail.slidePosition;
                x2 = x1 + thumbnailWidth;
            } else {
                x1 = box.x2 - thumbnailWidth + slideOffset * thumbnail.slidePosition;
                x2 = x1 + thumbnailWidth;
            }

            if (i == this._dropPlaceholderPos) {
                let [minHeight, placeholderHeight] = this._dropPlaceholder.get_preferred_height(-1);
                childBox.x1 = x1;
                childBox.x2 = x1 + thumbnailWidth;
                childBox.y1 = Math.round(y);
                childBox.y2 = Math.round(y + placeholderHeight);
                this._dropPlaceholder.allocate(childBox, flags);
                Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                    this._dropPlaceholder.show();
                });
                y += placeholderHeight + spacing;
            }

            // We might end up with thumbnailHeight being something like 99.33
            // pixels. To make this work and not end up with a gap at the bottom,
            // we need some thumbnails to be 99 pixels and some 100 pixels height;
            // we compute an actual scale separately for each thumbnail.
            let y1 = Math.round(y);
            let y2 = Math.round(y + thumbnailHeight);
            let roundedVScale = (y2 - y1) / portholeHeight;

            if (thumbnail.metaWorkspace == indicatorWorkspace) {
                indicatorY1 = y1;
                indicatorY2 = y2;
            }

            // Allocating a scaled actor is funny - x1/y1 correspond to the origin
            // of the actor, but x2/y2 are increased by the *unscaled* size.
            childBox.x1 = x1;
            childBox.x2 = x1 + portholeWidth;
            childBox.y1 = y1;
            childBox.y2 = y1 + portholeHeight;

            thumbnail.actor.set_scale(roundedHScale, roundedVScale);
            thumbnail.actor.allocate(childBox, flags);

            // We round the collapsing portion so that we don't get thumbnails resizing
            // during an animation due to differences in rounded, but leave the uncollapsed
            // portion unrounded so that non-animating we end up with the right total
            y += thumbnailHeight - Math.round(thumbnailHeight * thumbnail.collapseFraction);
        }

        if (rtl) {
            childBox.x1 = box.x1;
            childBox.x2 = box.x1 + thumbnailWidth;
        } else {
            childBox.x1 = box.x2 - thumbnailWidth;
            childBox.x2 = box.x2;
        }
        childBox.x1 -= indicatorLeftFullBorder;
        childBox.x2 += indicatorRightFullBorder;
        childBox.y1 = indicatorY1 - indicatorTopFullBorder;
        childBox.y2 = (indicatorY2 ? indicatorY2 : (indicatorY1 + thumbnailHeight)) + indicatorBottomFullBorder;
        this._indicator.allocate(childBox, flags);
    }

    _activeWorkspaceChanged(wm, from, to, direction) {
        let thumbnail;
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        for (let i = 0; i < this._thumbnails.length; i++) {
            if (this._thumbnails[i].metaWorkspace == activeWorkspace) {
                thumbnail = this._thumbnails[i];
                break;
            }
        }

        this._animatingIndicator = true;
        let indicatorThemeNode = this._indicator.get_theme_node();
        let indicatorTopFullBorder = indicatorThemeNode.get_padding(St.Side.TOP) + indicatorThemeNode.get_border_width(St.Side.TOP);
        this.indicatorY = this._indicator.allocation.y1 + indicatorTopFullBorder;
        Tweener.addTween(this,
                         { indicatorY: thumbnail.actor.allocation.y1,
                           time: WorkspacesView.WORKSPACE_SWITCH_TIME,
                           transition: 'easeOutQuad',
                           onComplete() {
                               this._animatingIndicator = false;
                               this._queueUpdateStates();
                           },
                           onCompleteScope: this
                         });
    }
});
(uuay)extensionUtils.js=// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

// Common utils for the extension system and the extension
// preferences tool

const { Gio, GLib } = imports.gi;

const Gettext = imports.gettext;
const Lang = imports.lang;

const Config = imports.misc.config;

var ExtensionType = {
    SYSTEM: 1,
    PER_USER: 2,
    SESSION_MODE: 3
};

var ExtensionState = {
    ENABLED: 1,
    DISABLED: 2,
    ERROR: 3,
    OUT_OF_DATE: 4,
    DOWNLOADING: 5,
    INITIALIZED: 6,

    // Used as an error state for operations on unknown extensions,
    // should never be in a real extensionMeta object.
    UNINSTALLED: 99
};

const SERIALIZED_PROPERTIES = [
    'type',
    'state',
    'path',
    'error',
    'hasPrefs',
    'hasUpdate',
    'canChange',
];

/**
 * getCurrentExtension:
 *
 * Returns the current extension, or null if not called from an extension.
 */
function getCurrentExtension() {
    let stack = (new Error()).stack.split('\n');
    let extensionStackLine;

    // Search for an occurrence of an extension stack frame
    // Start at 1 because 0 is the stack frame of this function
    for (let i = 1; i < stack.length; i++) {
        if (stack[i].indexOf('/gnome-shell/extensions/') > -1) {
            extensionStackLine = stack[i];
            break;
        }
    }
    if (!extensionStackLine)
        return null;

    // The stack line is like:
    //   init([object Object])@/home/user/data/gnome-shell/extensions/u@u.id/prefs.js:8
    //
    // In the case that we're importing from
    // module scope, the first field is blank:
    //   @/home/user/data/gnome-shell/extensions/u@u.id/prefs.js:8
    let match = new RegExp('@(.+):\\d+').exec(extensionStackLine);
    if (!match)
        return null;

    // local import, as the module is used from outside the gnome-shell process
    // as well (not this function though)
    let extensionManager = imports.ui.main.extensionManager;

    let path = match[1];
    let file = Gio.File.new_for_path(path);

    // Walk up the directory tree, looking for an extension with
    // the same UUID as a directory name.
    while (file != null) {
        let extension = extensionManager.lookup(file.get_basename());
        if (extension !== undefined)
            return extension;
        file = file.get_parent();
    }

    return null;
}

/**
 * initTranslations:
 * @domain: (optional): the gettext domain to use
 *
 * Initialize Gettext to load translations from extensionsdir/locale.
 * If @domain is not provided, it will be taken from metadata['gettext-domain']
 */
function initTranslations(domain) {
    let extension = getCurrentExtension();

    if (!extension)
        throw new Error('initTranslations() can only be called from extensions');

    domain = domain || extension.metadata['gettext-domain'];

    // Expect USER extensions to have a locale/ subfolder, otherwise assume a
    // SYSTEM extension that has been installed in the same prefix as the shell
    let localeDir = extension.dir.get_child('locale');
    if (localeDir.query_exists(null))
        Gettext.bindtextdomain(domain, localeDir.get_path());
    else
        Gettext.bindtextdomain(domain, Config.LOCALEDIR);
}

/**
 * getSettings:
 * @schema: (optional): the GSettings schema id
 *
 * Builds and returns a GSettings schema for @schema, using schema files
 * in extensionsdir/schemas. If @schema is omitted, it is taken from
 * metadata['settings-schema'].
 */
function getSettings(schema) {
    let extension = getCurrentExtension();

    if (!extension)
        throw new Error('getSettings() can only be called from extensions');

    schema = schema || extension.metadata['settings-schema'];

    const GioSSS = Gio.SettingsSchemaSource;

    // Expect USER extensions to have a schemas/ subfolder, otherwise assume a
    // SYSTEM extension that has been installed in the same prefix as the shell
    let schemaDir = extension.dir.get_child('schemas');
    let schemaSource;
    if (schemaDir.query_exists(null))
        schemaSource = GioSSS.new_from_directory(schemaDir.get_path(),
                                                 GioSSS.get_default(),
                                                 false);
    else
        schemaSource = GioSSS.get_default();

    let schemaObj = schemaSource.lookup(schema, true);
    if (!schemaObj)
        throw new Error(`Schema ${schema} could not be found for extension ${extension.metadata.uuid}. Please check your installation`);

    return new Gio.Settings({ settings_schema: schemaObj });
}

/**
 * versionCheck:
 * @required: an array of versions we're compatible with
 * @current: the version we have
 *
 * Check if a component is compatible for an extension.
 * @required is an array, and at least one version must match.
 * @current must be in the format <major>.<minor>.<point>.<micro>
 * <micro> is always ignored
 * <point> is ignored if <minor> is even (so you can target the
 * whole stable release)
 * <minor> and <major> must match
 * Each target version must be at least <major> and <minor>
 */
function versionCheck(required, current) {
    let currentArray = current.split('.');
    let major = currentArray[0];
    let minor = currentArray[1];
    let point = currentArray[2];
    for (let i = 0; i < required.length; i++) {
        let requiredArray = required[i].split('.');
        if (requiredArray[0] == major &&
            requiredArray[1] == minor &&
            (requiredArray[2] == point ||
             (requiredArray[2] == undefined && parseInt(minor) % 2 == 0)))
            return true;
    }
    return false;
}

function isOutOfDate(extension) {
    if (!versionCheck(extension.metadata['shell-version'], Config.PACKAGE_VERSION))
        return true;

    return false;
}

function serializeExtension(extension) {
    let obj = {};
    Lang.copyProperties(extension.metadata, obj);

    SERIALIZED_PROPERTIES.forEach(prop => {
        obj[prop] = extension[prop];
    });

    let res = {};
    for (let key in obj) {
        let val = obj[key];
        let type;
        switch (typeof val) {
        case 'string':
            type = 's';
            break;
        case 'number':
            type = 'd';
            break;
        case 'boolean':
            type = 'b';
            break;
        default:
            continue;
        }
        res[key] = GLib.Variant.new(type, val);
    }

    return res;
}

function deserializeExtension(variant) {
    let res = { metadata: {} };
    for (let prop in variant) {
        let val = variant[prop].unpack();
        if (SERIALIZED_PROPERTIES.includes(prop))
            res[prop] = val;
        else
            res.metadata[prop] = val;
    }
    // add the 2 additional properties to create a valid extension object, as createExtensionObject()
    res.uuid = res.metadata.uuid;
    res.dir = Gio.File.new_for_path(res.path);
    return res;
}

function installImporter(extension) {
    let oldSearchPath = imports.searchPath.slice();  // make a copy
    imports.searchPath = [extension.dir.get_parent().get_path()];
    // importing a "subdir" creates a new importer object that doesn't affect
    // the global one
    extension.imports = imports[extension.uuid];
    imports.searchPath = oldSearchPath;
}
(uuay)volume.js�,// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, Gvc, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;

const ALLOW_AMPLIFIED_VOLUME_KEY = 'allow-volume-above-100-percent';

var VOLUME_NOTIFY_ID = 1;

// Each Gvc.MixerControl is a connection to PulseAudio,
// so it's better to make it a singleton
let _mixerControl;
function getMixerControl() {
    if (_mixerControl)
        return _mixerControl;

    _mixerControl = new Gvc.MixerControl({ name: 'GNOME Shell Volume Control' });
    _mixerControl.open();

    return _mixerControl;
}

var StreamSlider = class {
    constructor(control) {
        this._control = control;

        this.item = new PopupMenu.PopupBaseMenuItem({ activate: false });
        this.item.actor.hide();

        this._slider = new Slider.Slider(0);

        this._soundSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.sound' });
        this._soundSettings.connect('changed::' + ALLOW_AMPLIFIED_VOLUME_KEY, this._amplifySettingsChanged.bind(this));
        this._amplifySettingsChanged();

        this._slider.connect('value-changed', this._sliderChanged.bind(this));
        this._slider.connect('drag-end', this._notifyVolumeChange.bind(this));

        this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
        this.item.actor.add(this._icon);
        this.item.actor.add(this._slider.actor, { expand: true });
        this.item.actor.connect('button-press-event', (actor, event) => {
            return this._slider.startDragging(event);
        });
        this.item.actor.connect('key-press-event', (actor, event) => {
            return this._slider.onKeyPressEvent(actor, event);
        });

        this._stream = null;
        this._volumeCancellable = null;
    }

    get stream() {
        return this._stream;
    }

    set stream(stream) {
        if (this._stream) {
            this._disconnectStream(this._stream);
        }

        this._stream = stream;

        if (this._stream) {
            this._connectStream(this._stream);
            this._updateVolume();
        } else {
            this.emit('stream-updated');
        }

        this._updateVisibility();
    }

    _disconnectStream(stream) {
        stream.disconnect(this._mutedChangedId);
        this._mutedChangedId = 0;
        stream.disconnect(this._volumeChangedId);
        this._volumeChangedId = 0;
    }

    _connectStream(stream) {
        this._mutedChangedId = stream.connect('notify::is-muted', this._updateVolume.bind(this));
        this._volumeChangedId = stream.connect('notify::volume', this._updateVolume.bind(this));
    }

    _shouldBeVisible() {
        return this._stream != null;
    }

    _updateVisibility() {
        let visible = this._shouldBeVisible();
        this.item.actor.visible = visible;
    }

    scroll(event) {
        return this._slider.scroll(event);
    }

    setValue(value) {
        // piggy-back off of sliderChanged
        this._slider.setValue(value);
    }

    _sliderChanged(slider, value, property) {
        if (!this._stream)
            return;

        let volume = value * this._control.get_vol_max_norm();
        let prevMuted = this._stream.is_muted;
        if (volume < 1) {
            this._stream.volume = 0;
            if (!prevMuted)
                this._stream.change_is_muted(true);
        } else {
            this._stream.volume = volume;
            if (prevMuted)
                this._stream.change_is_muted(false);
        }
        this._stream.push_volume();
    }

    _notifyVolumeChange() {
        if (this._volumeCancellable)
            this._volumeCancellable.cancel();

        this._volumeCancellable = new Gio.Cancellable();
        let player = global.display.get_sound_player();
        player.play_from_theme('audio-volume-change',
                               _("Volume changed"),
                               this._volumeCancellable);
    }

    _updateVolume() {
        let muted = this._stream.is_muted;
        this._slider.setValue(muted ? 0 : (this._stream.volume / this._control.get_vol_max_norm()));
        this.emit('stream-updated');
    }

    _amplifySettingsChanged() {
        this._allowAmplified = this._soundSettings.get_boolean(ALLOW_AMPLIFIED_VOLUME_KEY);

        if (this._allowAmplified)
            this._slider.setMaximumValue(this.getMaxLevel() / 100);
        else
            this._slider.setMaximumValue(1);

        if (this._stream)
            this._updateVolume();
    }

    getIcon() {
        if (!this._stream)
            return null;

        let icons = ["audio-volume-muted-symbolic",
                     "audio-volume-low-symbolic",
                     "audio-volume-medium-symbolic",
                     "audio-volume-high-symbolic",
                     "audio-volume-overamplified-symbolic"];

        let volume = this._stream.volume;
        let n;
        if (this._stream.is_muted || volume <= 0) {
            n = 0;
        } else {
            n = Math.ceil(3 * volume / this._control.get_vol_max_norm());
            if (n < 1)
                n = 1;
            else if (n > 3)
                n = 4;
        }
        return icons[n];
    }

    getLevel() {
        if (!this._stream)
            return null;

        return 100 * this._stream.volume / this._control.get_vol_max_norm();
    }

    getMaxLevel() {
        let maxVolume = this._control.get_vol_max_norm();
        if (this._allowAmplified)
            maxVolume = this._control.get_vol_max_amplified();

        return 100 * maxVolume / this._control.get_vol_max_norm();
    }
};
Signals.addSignalMethods(StreamSlider.prototype);

var OutputStreamSlider = class extends StreamSlider {
    constructor(control) {
        super(control);
        this._slider.actor.accessible_name = _("Volume");
    }

    _connectStream(stream) {
        super._connectStream(stream);
        this._portChangedId = stream.connect('notify::port', this._portChanged.bind(this));
        this._portChanged();
    }

    _findHeadphones(sink) {
        // This only works for external headphones (e.g. bluetooth)
        if (sink.get_form_factor() == 'headset' ||
            sink.get_form_factor() == 'headphone')
            return true;

        // a bit hackish, but ALSA/PulseAudio have a number
        // of different identifiers for headphones, and I could
        // not find the complete list
        if (sink.get_ports().length > 0)
            return sink.get_port().port.indexOf('headphone') >= 0;

        return false;
    }

    _disconnectStream(stream) {
        super._disconnectStream(stream);
        stream.disconnect(this._portChangedId);
        this._portChangedId = 0;
    }

    _updateSliderIcon() {
        this._icon.icon_name = (this._hasHeadphones ?
                                'audio-headphones-symbolic' :
                                'audio-speakers-symbolic');
    }

    _portChanged() {
        let hasHeadphones = this._findHeadphones(this._stream);
        if (hasHeadphones != this._hasHeadphones) {
            this._hasHeadphones = hasHeadphones;
            this._updateSliderIcon();
        }
    }
};

var InputStreamSlider = class extends StreamSlider {
    constructor(control) {
        super(control);
        this._slider.actor.accessible_name = _("Microphone");
        this._control.connect('stream-added', this._maybeShowInput.bind(this));
        this._control.connect('stream-removed', this._maybeShowInput.bind(this));
        this._icon.icon_name = 'audio-input-microphone-symbolic';
    }

    _connectStream(stream) {
        super._connectStream(stream);
        this._maybeShowInput();
    }

    _maybeShowInput() {
        // only show input widgets if any application is recording audio
        let showInput = false;
        let recordingApps = this._control.get_source_outputs();
        if (this._stream && recordingApps) {
            for (let i = 0; i < recordingApps.length; i++) {
                let outputStream = recordingApps[i];
                let id = outputStream.get_application_id();
                // but skip gnome-volume-control and pavucontrol
                // (that appear as recording because they show the input level)
                if (!id || (id != 'org.gnome.VolumeControl' && id != 'org.PulseAudio.pavucontrol')) {
                    showInput = true;
                    break;
                }
            }
        }

        this._showInput = showInput;
        this._updateVisibility();
    }

    _shouldBeVisible() {
        return super._shouldBeVisible() && this._showInput;
    }
};

var VolumeMenu = class extends PopupMenu.PopupMenuSection {
    constructor(control) {
        super();

        this.hasHeadphones = false;

        this._control = control;
        this._control.connect('state-changed', this._onControlStateChanged.bind(this));
        this._control.connect('default-sink-changed', this._readOutput.bind(this));
        this._control.connect('default-source-changed', this._readInput.bind(this));

        this._output = new OutputStreamSlider(this._control);
        this._output.connect('stream-updated', () => {
            this.emit('icon-changed');
        });
        this.addMenuItem(this._output.item);

        this._input = new InputStreamSlider(this._control);
        this.addMenuItem(this._input.item);

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

        this._onControlStateChanged();
    }

    scroll(event) {
        return this._output.scroll(event);
    }

    _onControlStateChanged() {
        if (this._control.get_state() == Gvc.MixerControlState.READY) {
            this._readInput();
            this._readOutput();
        } else {
            this.emit('icon-changed');
        }
    }

    _readOutput() {
        this._output.stream = this._control.get_default_sink();
    }

    _readInput() {
        this._input.stream = this._control.get_default_source();
    }

    getIcon() {
        return this._output.getIcon();
    }

    getLevel() {
        return this._output.getLevel();
    }

    getMaxLevel() {
        return this._output.getMaxLevel();
    }
};

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._primaryIndicator = this._addIndicator();

        this._control = getMixerControl();
        this._volumeMenu = new VolumeMenu(this._control);
        this._volumeMenu.connect('icon-changed', menu => {
            let icon = this._volumeMenu.getIcon();

            if (icon != null) {
                this.indicators.show();
                this._primaryIndicator.icon_name = icon;
            } else {
                this.indicators.hide();
            }
        });

        this.menu.addMenuItem(this._volumeMenu);

        this.indicators.connect('scroll-event', this._onScrollEvent.bind(this));
    }

    _onScrollEvent(actor, event) {
        let result = this._volumeMenu.scroll(event);
        if (result == Clutter.EVENT_PROPAGATE || this.menu.actor.mapped)
            return result;

        let gicon = new Gio.ThemedIcon({ name: this._volumeMenu.getIcon() });
        let level = parseInt(this._volumeMenu.getLevel());
        let maxLevel = parseInt(this._volumeMenu.getMaxLevel());
        Main.osdWindowManager.show(-1, gicon, null, level, maxLevel);
        return result;
    }
};
(uuay)workspaceSwitcherPopup.js#// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, GObject, Meta, St } = imports.gi;
const Mainloop = imports.mainloop;

const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var ANIMATION_TIME = 0.1;
var DISPLAY_TIMEOUT = 600;

var WorkspaceSwitcherPopupList = GObject.registerClass(
class WorkspaceSwitcherPopupList extends St.Widget {
    _init() {
        super._init({ style_class: 'workspace-switcher' });

        this._itemSpacing = 0;
        this._childHeight = 0;
        this._childWidth = 0;
        this._orientation = global.workspace_manager.layout_rows == -1
            ? Clutter.Orientation.VERTICAL
            : Clutter.Orientation.HORIZONTAL;

        this.connect('style-changed', () => {
           this._itemSpacing = this.get_theme_node().get_length('spacing');
        });
    }

    _getPreferredSizeForOrientation(forSize) {
        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
        let themeNode = this.get_theme_node();

        let availSize;
        if (this._orientation == Clutter.Orientation.HORIZONTAL)
            availSize = workArea.width - themeNode.get_horizontal_padding();
        else
            availSize = workArea.height - themeNode.get_vertical_padding();

        let size = 0;
        for (let child of this.get_children()) {
            let [childMinHeight, childNaturalHeight] = child.get_preferred_height(-1);
            let height = childNaturalHeight * workArea.width / workArea.height;

            if (this._orientation == Clutter.Orientation.HORIZONTAL) {
                size += height * workArea.width / workArea.height;
            } else {
                size += height;
            }
        }

        let workspaceManager = global.workspace_manager;
        let spacing = this._itemSpacing * (workspaceManager.n_workspaces - 1);
        size += spacing;
        size = Math.min(size, availSize);

        if (this._orientation == Clutter.Orientation.HORIZONTAL) {
            this._childWidth = (size - spacing) / workspaceManager.n_workspaces;
            return themeNode.adjust_preferred_width(size, size);
        } else {
            this._childHeight = (size - spacing) / workspaceManager.n_workspaces;
            return themeNode.adjust_preferred_height(size, size);
        }
    }

    _getSizeForOppositeOrientation() {
        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);

        if (this._orientation == Clutter.Orientation.HORIZONTAL) {
            this._childHeight = Math.round(this._childWidth * workArea.height / workArea.width);
            return [this._childHeight, this._childHeight];
        } else {
            this._childWidth = Math.round(this._childHeight * workArea.width / workArea.height);
            return [this._childWidth, this._childWidth];
        }
    }

    vfunc_get_preferred_height(forWidth) {
        if (this._orientation == Clutter.Orientation.HORIZONTAL)
            return this._getSizeForOppositeOrientation();
        else
            return this._getPreferredSizeForOrientation(forWidth);
    }

    vfunc_get_preferred_width(forHeight) {
        if (this._orientation == Clutter.Orientation.HORIZONTAL)
            return this._getPreferredSizeForOrientation(forHeight);
        else
            return this._getSizeForOppositeOrientation();
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let themeNode = this.get_theme_node();
        box = themeNode.get_content_box(box);

        let childBox = new Clutter.ActorBox();

        let rtl = this.text_direction == Clutter.TextDirection.RTL;
        let x = rtl ? box.x2 - this._childWidth : box.x1;
        let y = box.y1;
        for (let child of this.get_children()) {
            childBox.x1 = Math.round(x);
            childBox.x2 = Math.round(x + this._childWidth);
            childBox.y1 = Math.round(y);
            childBox.y2 = Math.round(y + this._childHeight);

            if (this._orientation == Clutter.Orientation.HORIZONTAL) {
                if (rtl)
                    x -= this._childWidth + this._itemSpacing;
                else
                    x += this._childWidth + this._itemSpacing;
            } else {
                y += this._childHeight + this._itemSpacing;
            }
            child.allocate(childBox, flags);
        }
    }
});

var WorkspaceSwitcherPopup = GObject.registerClass(
class WorkspaceSwitcherPopup extends St.Widget {
    _init() {
        super._init({ x: 0,
                      y: 0,
                      width: global.screen_width,
                      height: global.screen_height,
                      style_class: 'workspace-switcher-group' });

        this.actor = this;

        Main.uiGroup.add_actor(this);

        this._timeoutId = 0;

        this._container = new St.BoxLayout({ style_class: 'workspace-switcher-container' });
        this.add_child(this._container);

        this._list = new WorkspaceSwitcherPopupList();
        this._container.add_child(this._list);

        this._redisplay();

        this.hide();

        let workspaceManager = global.workspace_manager;
        this._workspaceManagerSignals = [];
        this._workspaceManagerSignals.push(workspaceManager.connect('workspace-added',
                                                                    this._redisplay.bind(this)));
        this._workspaceManagerSignals.push(workspaceManager.connect('workspace-removed',
                                                                    this._redisplay.bind(this)));

        this.connect('destroy', this._onDestroy.bind(this));
    }

    _redisplay() {
        let workspaceManager = global.workspace_manager;

        this._list.destroy_all_children();

        for (let i = 0; i < workspaceManager.n_workspaces; i++) {
            let indicator = null;

           if (i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.UP)
               indicator = new St.Bin({ style_class: 'ws-switcher-active-up' });
           else if(i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.DOWN)
               indicator = new St.Bin({ style_class: 'ws-switcher-active-down' });
           else if(i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.LEFT)
               indicator = new St.Bin({ style_class: 'ws-switcher-active-left' });
           else if(i == this._activeWorkspaceIndex && this._direction == Meta.MotionDirection.RIGHT)
               indicator = new St.Bin({ style_class: 'ws-switcher-active-right' });
           else
               indicator = new St.Bin({ style_class: 'ws-switcher-box' });

           this._list.add_actor(indicator);

        }

        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
        let [containerMinHeight, containerNatHeight] = this._container.get_preferred_height(global.screen_width);
        let [containerMinWidth, containerNatWidth] = this._container.get_preferred_width(containerNatHeight);
        this._container.x = workArea.x + Math.floor((workArea.width - containerNatWidth) / 2);
        this._container.y = workArea.y + Math.floor((workArea.height - containerNatHeight) / 2);
    }

    _show() {
        Tweener.addTween(this._container, { opacity: 255,
                                            time: ANIMATION_TIME,
                                            transition: 'easeOutQuad'
                                           });
        this.actor.show();
    }

    display(direction, activeWorkspaceIndex) {
        this._direction = direction;
        this._activeWorkspaceIndex = activeWorkspaceIndex;

        this._redisplay();
        if (this._timeoutId != 0)
            Mainloop.source_remove(this._timeoutId);
        this._timeoutId = Mainloop.timeout_add(DISPLAY_TIMEOUT, this._onTimeout.bind(this));
        GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._onTimeout');
        this._show();
    }

    _onTimeout() {
        Mainloop.source_remove(this._timeoutId);
        this._timeoutId = 0;
        Tweener.addTween(this._container, { opacity: 0.0,
                                            time: ANIMATION_TIME,
                                            transition: 'easeOutQuad',
                                            onComplete() { this.destroy(); },
                                            onCompleteScope: this
                                           });
        return GLib.SOURCE_REMOVE;
    }

    _onDestroy() {
        if (this._timeoutId)
            Mainloop.source_remove(this._timeoutId);
        this._timeoutId = 0;

        let workspaceManager = global.workspace_manager;
        for (let i = 0; i < this._workspaceManagerSignals.length; i++)
            workspaceManager.disconnect(this._workspaceManagerSignals[i]);

        this._workspaceManagerSignals = [];
    }
});
(uuay)osdWindow.jsD"// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, GObject, Meta, St } = imports.gi;
const Mainloop = imports.mainloop;

const BarLevel = imports.ui.barLevel;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var HIDE_TIMEOUT = 1500;
var FADE_TIME = 0.1;
var LEVEL_ANIMATION_TIME = 0.1;

var LevelBar = class extends BarLevel.BarLevel {
    constructor() {
        super(0, { styleClass: 'level' });

        this._level = 0;
        this._maxLevel = 100;

        this.actor.accessible_name = _("Volume");

        this.actor.connect('notify::width', () => { this.level = this.level; });
    }

    get level() {
        return this._level;
    }

    set level(value) {
        this._level = Math.max(0, Math.min(value, this._maxLevel));

        this.setValue(this._level / 100);
    }

    get maxLevel() {
        return this._maxLevel;
    }

    set maxLevel(value) {
        this._maxLevel = Math.max(100, value);

        this.setMaximumValue(this._maxLevel / 100);
    }
};

var OsdWindowConstraint = GObject.registerClass(
class OsdWindowConstraint extends Clutter.Constraint {
    _init(props) {
        this._minSize = 0;
        super._init(props);
    }

    set minSize(v) {
        this._minSize = v;
        if (this.actor)
            this.actor.queue_relayout();
    }

    vfunc_update_allocation(actor, actorBox) {
        // Clutter will adjust the allocation for margins,
        // so add it to our minimum size
        let minSize = this._minSize + actor.margin_top + actor.margin_bottom;
        let [width, height] = actorBox.get_size();

        // Enforce a ratio of 1
        let size = Math.ceil(Math.max(minSize, height));
        actorBox.set_size(size, size);

        // Recenter
        let [x, y] = actorBox.get_origin();
        actorBox.set_origin(Math.ceil(x + width / 2 - size / 2),
                            Math.ceil(y + height / 2 - size / 2));
    }
});

var OsdWindow = class {
    constructor(monitorIndex) {
        this.actor = new St.Widget({ x_expand: true,
                                     y_expand: true,
                                     x_align: Clutter.ActorAlign.CENTER,
                                     y_align: Clutter.ActorAlign.CENTER });

        this._monitorIndex = monitorIndex;
        let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
        this.actor.add_constraint(constraint);

        this._boxConstraint = new OsdWindowConstraint();
        this._box = new St.BoxLayout({ style_class: 'osd-window',
                                       vertical: true });
        this._box.add_constraint(this._boxConstraint);
        this.actor.add_actor(this._box);

        this._icon = new St.Icon();
        this._box.add(this._icon, { expand: true });

        this._label = new St.Label();
        this._box.add(this._label);

        this._level = new LevelBar();
        this._box.add(this._level.actor);

        this._hideTimeoutId = 0;
        this._reset();

        this.actor.connect('destroy', this._onDestroy.bind(this));

        this._monitorsChangedId =
            Main.layoutManager.connect('monitors-changed',
                                       this._relayout.bind(this));
        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        this._scaleChangedId =
            themeContext.connect('notify::scale-factor',
                                 this._relayout.bind(this));
        this._relayout();
        Main.uiGroup.add_child(this.actor);
    }

    _onDestroy() {
        if (this._monitorsChangedId)
            Main.layoutManager.disconnect(this._monitorsChangedId);
        this._monitorsChangedId = 0;

        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        if (this._scaleChangedId)
            themeContext.disconnect(this._scaleChangedId);
        this._scaleChangedId = 0;
    }

    setIcon(icon) {
        this._icon.gicon = icon;
    }

    setLabel(label) {
        this._label.visible = (label != undefined);
        if (label)
            this._label.text = label;
    }

    setLevel(level) {
        this._level.actor.visible = (level != undefined);
        if (level != undefined) {
            if (this.actor.visible)
                Tweener.addTween(this._level,
                                 { level: level,
                                   time: LEVEL_ANIMATION_TIME,
                                   transition: 'easeOutQuad' });
            else
                this._level.level = level;
        }
    }

    setMaxLevel(maxLevel) {
        if (maxLevel === undefined)
            maxLevel = 100;
        this._level.maxLevel = maxLevel;
    }

    show() {
        if (!this._icon.gicon)
            return;

        if (!this.actor.visible) {
            Meta.disable_unredirect_for_display(global.display);
            this.actor.show();
            this.actor.opacity = 0;
            this.actor.get_parent().set_child_above_sibling(this.actor, null);

            Tweener.addTween(this.actor,
                             { opacity: 255,
                               time: FADE_TIME,
                               transition: 'easeOutQuad' });
        }

        if (this._hideTimeoutId)
            Mainloop.source_remove(this._hideTimeoutId);
        this._hideTimeoutId = Mainloop.timeout_add(HIDE_TIMEOUT,
                                                   this._hide.bind(this));
        GLib.Source.set_name_by_id(this._hideTimeoutId, '[gnome-shell] this._hide');
    }

    cancel() {
        if (!this._hideTimeoutId)
            return;

        Mainloop.source_remove(this._hideTimeoutId);
        this._hide();
    }

    _hide() {
        this._hideTimeoutId = 0;
        Tweener.addTween(this.actor,
                         { opacity: 0,
                           time: FADE_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                              this._reset();
                              Meta.enable_unredirect_for_display(global.display);
                           }
                         });
        return GLib.SOURCE_REMOVE;
    }

    _reset() {
        this.actor.hide();
        this.setLabel(null);
        this.setMaxLevel(null);
        this.setLevel(null);
    }

    _relayout() {
        /* assume 110x110 on a 640x480 display and scale from there */
        let monitor = Main.layoutManager.monitors[this._monitorIndex];
        if (!monitor)
            return; // we are about to be removed

        let scalew = monitor.width / 640.0;
        let scaleh = monitor.height / 480.0;
        let scale = Math.min(scalew, scaleh);
        let popupSize = 110 * Math.max(1, scale);

        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        this._icon.icon_size = popupSize / (2 * scaleFactor);
        this._box.translation_y = Math.round(monitor.height / 4);
        this._boxConstraint.minSize = popupSize;
    }
};

var OsdWindowManager = class {
    constructor() {
        this._osdWindows = [];
        Main.layoutManager.connect('monitors-changed',
                                    this._monitorsChanged.bind(this));
        this._monitorsChanged();
    }

    _monitorsChanged() {
        for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
            if (this._osdWindows[i] == undefined)
                this._osdWindows[i] = new OsdWindow(i);
        }

        for (let i = Main.layoutManager.monitors.length; i < this._osdWindows.length; i++) {
            this._osdWindows[i].actor.destroy();
            this._osdWindows[i] = null;
        }

        this._osdWindows.length = Main.layoutManager.monitors.length;
    }

    _showOsdWindow(monitorIndex, icon, label, level, maxLevel) {
        this._osdWindows[monitorIndex].setIcon(icon);
        this._osdWindows[monitorIndex].setLabel(label);
        this._osdWindows[monitorIndex].setMaxLevel(maxLevel);
        this._osdWindows[monitorIndex].setLevel(level);
        this._osdWindows[monitorIndex].show();
    }

    show(monitorIndex, icon, label, level, maxLevel) {
        if (monitorIndex != -1) {
            for (let i = 0; i < this._osdWindows.length; i++) {
                if (i == monitorIndex)
                    this._showOsdWindow(i, icon, label, level, maxLevel);
                else
                    this._osdWindows[i].cancel();
            }
        } else {
            for (let i = 0; i < this._osdWindows.length; i++)
                this._showOsdWindow(i, icon, label, level, maxLevel);
        }
    }

    hideAll() {
        for (let i = 0; i < this._osdWindows.length; i++)
            this._osdWindows[i].cancel();
    }
};
(uuay)altTab.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;

const Main = imports.ui.main;
const SwitcherPopup = imports.ui.switcherPopup;
const Tweener = imports.ui.tweener;

var APP_ICON_HOVER_TIMEOUT = 200; // milliseconds

var THUMBNAIL_DEFAULT_SIZE = 256;
var THUMBNAIL_POPUP_TIME = 500; // milliseconds
var THUMBNAIL_FADE_TIME = 0.1; // seconds

var WINDOW_PREVIEW_SIZE = 128;
var APP_ICON_SIZE = 96;
var APP_ICON_SIZE_SMALL = 48;

const baseIconSizes = [96, 64, 48, 32, 22];

var AppIconMode = {
    THUMBNAIL_ONLY: 1,
    APP_ICON_ONLY: 2,
    BOTH: 3,
};

function _createWindowClone(window, size) {
    let [width, height] = window.get_size();
    let scale = Math.min(1.0, size / width, size / height);
    return new Clutter.Clone({ source: window,
                               width: width * scale,
                               height: height * scale,
                               x_align: Clutter.ActorAlign.CENTER,
                               y_align: Clutter.ActorAlign.CENTER,
                               // usual hack for the usual bug in ClutterBinLayout...
                               x_expand: true,
                               y_expand: true });
};

function getWindows(workspace) {
    // We ignore skip-taskbar windows in switchers, but if they are attached
    // to their parent, their position in the MRU list may be more appropriate
    // than the parent; so start with the complete list ...
    let windows = global.display.get_tab_list(Meta.TabList.NORMAL_ALL,
                                              workspace);
    // ... map windows to their parent where appropriate ...
    return windows.map(w => {
        return w.is_attached_dialog() ? w.get_transient_for() : w;
    // ... and filter out skip-taskbar windows and duplicates
    }).filter((w, i, a) => !w.skip_taskbar && a.indexOf(w) == i);
}

var AppSwitcherPopup = GObject.registerClass(
class AppSwitcherPopup extends SwitcherPopup.SwitcherPopup {
    _init() {
        super._init();

        this._thumbnails = null;
        this._thumbnailTimeoutId = 0;
        this._currentWindow = -1;

        this.thumbnailsVisible = false;

        let apps = Shell.AppSystem.get_default().get_running ();

        if (apps.length == 0)
            return;

        this._switcherList = new AppSwitcher(apps, this);
        this._items = this._switcherList.icons;
    }

    vfunc_allocate(box, flags) {
        super.vfunc_allocate(box, flags);

        // Allocate the thumbnails
        // We try to avoid overflowing the screen so we base the resulting size on
        // those calculations
        if (this._thumbnails) {
            let childBox = this._switcherList.get_allocation_box();
            let primary = Main.layoutManager.primaryMonitor;

            let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT);
            let rightPadding = this.get_theme_node().get_padding(St.Side.RIGHT);
            let bottomPadding = this.get_theme_node().get_padding(St.Side.BOTTOM);
            let hPadding = leftPadding + rightPadding;

            let icon = this._items[this._selectedIndex];
            let [posX, posY] = icon.get_transformed_position();
            let thumbnailCenter = posX + icon.width / 2;
            let [childMinWidth, childNaturalWidth] = this._thumbnails.get_preferred_width(-1);
            childBox.x1 = Math.max(primary.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
            if (childBox.x1 + childNaturalWidth > primary.x + primary.width - hPadding) {
                let offset = childBox.x1 + childNaturalWidth - primary.width + hPadding;
                childBox.x1 = Math.max(primary.x + leftPadding, childBox.x1 - offset - hPadding);
            }

            let spacing = this.get_theme_node().get_length('spacing');

            childBox.x2 = childBox.x1 +  childNaturalWidth;
            if (childBox.x2 > primary.x + primary.width - rightPadding)
                childBox.x2 = primary.x + primary.width - rightPadding;
            childBox.y1 = this._switcherList.allocation.y2 + spacing;
            this._thumbnails.addClones(primary.y + primary.height - bottomPadding - childBox.y1);
            let [childMinHeight, childNaturalHeight] = this._thumbnails.get_preferred_height(-1);
            childBox.y2 = childBox.y1 + childNaturalHeight;
            this._thumbnails.allocate(childBox, flags);
        }
    }

    _initialSelection(backward, binding) {
        if (binding == 'switch-group') {
            if (backward) {
                this._select(0, this._items[0].cachedWindows.length - 1);
            } else {
                if (this._items[0].cachedWindows.length > 1)
                    this._select(0, 1);
                else
                    this._select(0, 0);
            }
        } else if (binding == 'switch-group-backward') {
            this._select(0, this._items[0].cachedWindows.length - 1);
        } else if (binding == 'switch-applications-backward') {
            this._select(this._items.length - 1);
        } else if (this._items.length == 1) {
            this._select(0);
        } else if (backward) {
            this._select(this._items.length - 1);
        } else {
            this._select(1);
        }
    }

    _nextWindow() {
        // We actually want the second window if we're in the unset state
        if (this._currentWindow == -1)
            this._currentWindow = 0;
        return SwitcherPopup.mod(this._currentWindow + 1,
                                 this._items[this._selectedIndex].cachedWindows.length);
    }

    _previousWindow() {
        // Also assume second window here
        if (this._currentWindow == -1)
            this._currentWindow = 1;
        return SwitcherPopup.mod(this._currentWindow - 1,
                                 this._items[this._selectedIndex].cachedWindows.length);
    }

    _closeAppWindow(appIndex, windowIndex) {
        let appIcon = this._items[appIndex];
        if (!appIcon)
            return;

        let window = appIcon.cachedWindows[windowIndex];
        if (!window)
            return;

        window.delete(global.get_current_time());
    }

    _quitApplication(appIndex) {
        let appIcon = this._items[appIndex];
        if (!appIcon)
            return;

        appIcon.app.request_quit();
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.SWITCH_GROUP) {
            if (!this._thumbnailsFocused)
                this._select(this._selectedIndex, 0);
            else
                this._select(this._selectedIndex, this._nextWindow());
        } else if (action == Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD) {
            this._select(this._selectedIndex, this._previousWindow());
        } else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS) {
            this._select(this._next());
        } else if (action == Meta.KeyBindingAction.SWITCH_APPLICATIONS_BACKWARD) {
            this._select(this._previous());
        } else if (keysym == Clutter.q) {
            this._quitApplication(this._selectedIndex);
        } else if (this._thumbnailsFocused) {
            if (keysym == Clutter.Left)
                this._select(this._selectedIndex, this._previousWindow());
            else if (keysym == Clutter.Right)
                this._select(this._selectedIndex, this._nextWindow());
            else if (keysym == Clutter.Up)
                this._select(this._selectedIndex, null, true);
            else if (keysym == Clutter.w || keysym == Clutter.F4)
                this._closeAppWindow(this._selectedIndex, this._currentWindow);
            else
                return Clutter.EVENT_PROPAGATE;
        } else {
            if (keysym == Clutter.Left)
                this._select(this._previous());
            else if (keysym == Clutter.Right)
                this._select(this._next());
            else if (keysym == Clutter.Down)
                this._select(this._selectedIndex, 0);
            else
                return Clutter.EVENT_PROPAGATE;
        }

        return Clutter.EVENT_STOP;
    }

    _scrollHandler(direction) {
        if (direction == Clutter.ScrollDirection.UP) {
            if (this._thumbnailsFocused) {
                if (this._currentWindow == 0 || this._currentWindow == -1)
                    this._select(this._previous());
                else
                    this._select(this._selectedIndex, this._previousWindow());
            } else {
                let nwindows = this._items[this._selectedIndex].cachedWindows.length;
                if (nwindows > 1)
                    this._select(this._selectedIndex, nwindows - 1);
                else
                    this._select(this._previous());
            }
        } else if (direction == Clutter.ScrollDirection.DOWN) {
            if (this._thumbnailsFocused) {
                if (this._currentWindow == this._items[this._selectedIndex].cachedWindows.length - 1)
                    this._select(this._next());
                else
                    this._select(this._selectedIndex, this._nextWindow());
            } else {
                let nwindows = this._items[this._selectedIndex].cachedWindows.length;
                if (nwindows > 1)
                    this._select(this._selectedIndex, 0);
                else
                    this._select(this._next());
            }
        }
    }

    _itemActivatedHandler(n) {
        // If the user clicks on the selected app, activate the
        // selected window; otherwise (eg, they click on an app while
        // !mouseActive) activate the clicked-on app.
        if (n == this._selectedIndex && this._currentWindow >= 0)
            this._select(n, this._currentWindow);
        else
            this._select(n);
    }

    _itemEnteredHandler(n) {
        this._select(n);
    }

    _windowActivated(thumbnailList, n) {
        let appIcon = this._items[this._selectedIndex];
        Main.activateWindow(appIcon.cachedWindows[n]);
        this.fadeAndDestroy();
    }

    _windowEntered(thumbnailList, n) {
        if (!this.mouseActive)
            return;

        this._select(this._selectedIndex, n);
    }

    _windowRemoved(thumbnailList, n) {
        let appIcon = this._items[this._selectedIndex];
        if (!appIcon)
            return;

        if (appIcon.cachedWindows.length > 0) {
            let newIndex = Math.min(n, appIcon.cachedWindows.length - 1);
            this._select(this._selectedIndex, newIndex);
        }
    }

    _finish(timestamp) {
        let appIcon = this._items[this._selectedIndex];
        if (this._currentWindow < 0)
            appIcon.app.activate_window(appIcon.cachedWindows[0], timestamp);
        else if (appIcon.cachedWindows[this._currentWindow])
            Main.activateWindow(appIcon.cachedWindows[this._currentWindow], timestamp);

        super._finish(timestamp);
    }

    _onDestroy() {
        super._onDestroy();

        if (this._thumbnails)
            this._destroyThumbnails();
        if (this._thumbnailTimeoutId != 0)
            Mainloop.source_remove(this._thumbnailTimeoutId);
    }

    /**
     * _select:
     * @app: index of the app to select
     * @window: (optional) index of which of @app's windows to select
     * @forceAppFocus: optional flag, see below
     *
     * Selects the indicated @app, and optional @window, and sets
     * this._thumbnailsFocused appropriately to indicate whether the
     * arrow keys should act on the app list or the thumbnail list.
     *
     * If @app is specified and @window is unspecified or %null, then
     * the app is highlighted (ie, given a light background), and the
     * current thumbnail list, if any, is destroyed. If @app has
     * multiple windows, and @forceAppFocus is not %true, then a
     * timeout is started to open a thumbnail list.
     *
     * If @app and @window are specified (and @forceAppFocus is not),
     * then @app will be outlined, a thumbnail list will be created
     * and focused (if it hasn't been already), and the @window'th
     * window in it will be highlighted.
     *
     * If @app and @window are specified and @forceAppFocus is %true,
     * then @app will be highlighted, and @window outlined, and the
     * app list will have the keyboard focus.
     */
    _select(app, window, forceAppFocus) {
        if (app != this._selectedIndex || window == null) {
            if (this._thumbnails)
                this._destroyThumbnails();
        }

        if (this._thumbnailTimeoutId != 0) {
            Mainloop.source_remove(this._thumbnailTimeoutId);
            this._thumbnailTimeoutId = 0;
        }

        this._thumbnailsFocused = (window != null) && !forceAppFocus;

        this._selectedIndex = app;
        this._currentWindow = window ? window : -1;
        this._switcherList.highlight(app, this._thumbnailsFocused);

        if (window != null) {
            if (!this._thumbnails)
                this._createThumbnails();
            this._currentWindow = window;
            this._thumbnails.highlight(window, forceAppFocus);
        } else if (this._items[this._selectedIndex].cachedWindows.length > 1 &&
                   !forceAppFocus) {
            this._thumbnailTimeoutId = Mainloop.timeout_add (
                THUMBNAIL_POPUP_TIME,
                this._timeoutPopupThumbnails.bind(this));
            GLib.Source.set_name_by_id(this._thumbnailTimeoutId, '[gnome-shell] this._timeoutPopupThumbnails');
        }
    }

    _timeoutPopupThumbnails() {
        if (!this._thumbnails)
            this._createThumbnails();
        this._thumbnailTimeoutId = 0;
        this._thumbnailsFocused = false;
        return GLib.SOURCE_REMOVE;
    }

    _destroyThumbnails() {
        let thumbnailsActor = this._thumbnails;
        Tweener.addTween(thumbnailsActor,
                         { opacity: 0,
                           time: THUMBNAIL_FADE_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               thumbnailsActor.destroy();
                               this.thumbnailsVisible = false;
                           }
                         });
        this._thumbnails = null;
        if (this._switcherList._items[this._selectedIndex])
            this._switcherList._items[this._selectedIndex].remove_accessible_state (Atk.StateType.EXPANDED);
    }

    _createThumbnails() {
        this._thumbnails = new ThumbnailList (this._items[this._selectedIndex].cachedWindows);
        this._thumbnails.connect('item-activated', this._windowActivated.bind(this));
        this._thumbnails.connect('item-entered', this._windowEntered.bind(this));
        this._thumbnails.connect('item-removed', this._windowRemoved.bind(this));
        this._thumbnails.connect('destroy', () => {
            this._thumbnails = null;
            this._thumbnailsFocused = false;
        });

        this.add_actor(this._thumbnails);

        // Need to force an allocation so we can figure out whether we
        // need to scroll when selecting
        this._thumbnails.get_allocation_box();

        this._thumbnails.opacity = 0;
        Tweener.addTween(this._thumbnails,
                         { opacity: 255,
                           time: THUMBNAIL_FADE_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => { this.thumbnailsVisible = true; }
                         });

        this._switcherList._items[this._selectedIndex].add_accessible_state (Atk.StateType.EXPANDED);
    }
});

class CyclerHighlight {
    constructor() {
        this._window = null;

        this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout() });

        this._clone = new Clutter.Clone();
        this.actor.add_actor(this._clone);

        this._highlight = new St.Widget({ style_class: 'cycler-highlight' });
        this.actor.add_actor(this._highlight);

        let coordinate = Clutter.BindCoordinate.ALL;
        let constraint = new Clutter.BindConstraint({ coordinate: coordinate });
        this._clone.bind_property('source', constraint, 'source', 0);

        this.actor.add_constraint(constraint);

        this.actor.connect('notify::allocation',
                           this._onAllocationChanged.bind(this));
        this.actor.connect('destroy', this._onDestroy.bind(this));
    }

    set window(w) {
        if (this._window == w)
            return;

        this._window = w;

        if (this._clone.source)
            this._clone.source.sync_visibility();

        let windowActor = this._window ? this._window.get_compositor_private()
                                       : null;

        if (windowActor)
            windowActor.hide();

        this._clone.source = windowActor;
    }

    _onAllocationChanged() {
        if (!this._window) {
            this._highlight.set_size(0, 0);
            this._highlight.hide();
        } else {
            let [x, y] = this.actor.allocation.get_origin();
            let rect = this._window.get_frame_rect();
            this._highlight.set_size(rect.width, rect.height);
            this._highlight.set_position(rect.x - x, rect.y - y);
            this._highlight.show();
        }
    }

    _onDestroy() {
        this.window = null;
    }
};

// We don't show an actual popup, so just provide what SwitcherPopup
// expects instead of inheriting from SwitcherList
var CyclerList = GObject.registerClass({
    Signals: { 'item-activated': { param_types: [GObject.TYPE_INT] },
               'item-entered': { param_types: [GObject.TYPE_INT] },
               'item-removed': { param_types: [GObject.TYPE_INT] },
               'item-highlighted': { param_types: [GObject.TYPE_INT] } },
}, class CyclerList extends St.Widget {
    highlight(index, justOutline) {
        this.emit('item-highlighted', index);
    }
});

var CyclerPopup = GObject.registerClass(
class CyclerPopup extends SwitcherPopup.SwitcherPopup {
    _init() {
        if (new.target === CyclerPopup)
            throw new TypeError('Cannot instantiate abstract class ' + new.target.name);

        super._init();

        this._items = this._getWindows();

        if (this._items.length == 0)
            return;

        this._highlight = new CyclerHighlight();
        global.window_group.add_actor(this._highlight.actor);

        this._switcherList = new CyclerList();
        this._switcherList.connect('item-highlighted', (list, index) => {
            this._highlightItem(index);
        });
    }

    _highlightItem(index, justOutline) {
        this._highlight.window = this._items[index];
        global.window_group.set_child_above_sibling(this._highlight.actor, null);
    }

    _finish() {
        let window = this._items[this._selectedIndex];
        let ws = window.get_workspace();
        let workspaceManager = global.workspace_manager;
        let activeWs = workspaceManager.get_active_workspace();

        if (window.minimized) {
            Main.wm.skipNextEffect(window.get_compositor_private());
            window.unminimize();
        }

        if (activeWs == ws) {
            Main.activateWindow(window);
        } else {
            // If the selected window is on a different workspace, we don't
            // want it to disappear, then slide in with the workspace; instead,
            // always activate it on the active workspace ...
            activeWs.activate_with_focus(window, global.get_current_time());

            // ... then slide it over to the original workspace if necessary
            Main.wm.actionMoveWindow(window, ws);
        }

        super._finish();
    }

    _onDestroy() {
        this._highlight.actor.destroy();

        super._onDestroy();
    }
});


var GroupCyclerPopup = GObject.registerClass(
class GroupCyclerPopup extends CyclerPopup {
    _getWindows() {
        let app = Shell.WindowTracker.get_default().focus_app;
        return app ? app.get_windows() : [];
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.CYCLE_GROUP)
            this._select(this._next());
        else if (action == Meta.KeyBindingAction.CYCLE_GROUP_BACKWARD)
            this._select(this._previous());
        else
            return Clutter.EVENT_PROPAGATE;

        return Clutter.EVENT_STOP;
    }
});

var WindowSwitcherPopup = GObject.registerClass(
class WindowSwitcherPopup extends SwitcherPopup.SwitcherPopup {
    _init() {
        super._init();
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.window-switcher' });

        let windows = this._getWindowList();

        if (windows.length == 0)
            return;

        let mode = this._settings.get_enum('app-icon-mode');
        this._switcherList = new WindowList(windows, mode);
        this._items = this._switcherList.icons;
    }

    _getWindowList() {
        let workspace = null;

        if (this._settings.get_boolean('current-workspace-only')) {
            let workspaceManager = global.workspace_manager;

            workspace = workspaceManager.get_active_workspace();
        }

        return getWindows(workspace);
    }

    _closeWindow(windowIndex) {
        let windowIcon = this._items[windowIndex];
        if (!windowIcon)
            return;

        windowIcon.window.delete(global.get_current_time());
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.SWITCH_WINDOWS) {
            this._select(this._next());
        } else if (action == Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD) {
            this._select(this._previous());
        } else {
            if (keysym == Clutter.Left)
                this._select(this._previous());
            else if (keysym == Clutter.Right)
                this._select(this._next());
            else if (keysym == Clutter.w || keysym == Clutter.F4)
                this._closeWindow(this._selectedIndex);
            else
                return Clutter.EVENT_PROPAGATE;
        }

        return Clutter.EVENT_STOP;
    }

    _finish() {
        Main.activateWindow(this._items[this._selectedIndex].window);

        super._finish();
    }
});

var WindowCyclerPopup = GObject.registerClass(
class WindowCyclerPopup extends CyclerPopup {
    _init() {
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.window-switcher' });
        super._init();
    }

    _getWindows() {
        let workspace = null;

        if (this._settings.get_boolean('current-workspace-only')) {
            let workspaceManager = global.workspace_manager;

            workspace = workspaceManager.get_active_workspace();
        }

        return getWindows(workspace);
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.CYCLE_WINDOWS)
            this._select(this._next());
        else if (action == Meta.KeyBindingAction.CYCLE_WINDOWS_BACKWARD)
            this._select(this._previous());
        else
            return Clutter.EVENT_PROPAGATE;

        return Clutter.EVENT_STOP;
    }
});

var AppIcon = GObject.registerClass(
class AppIcon extends St.BoxLayout {
    _init(app) {
        super._init({ style_class: 'alt-tab-app',
                      vertical: true });

        this.app = app;
        this.icon = null;
        this._iconBin = new St.Bin({ x_fill: true, y_fill: true });

        this.add(this._iconBin, { x_fill: false, y_fill: false } );
        this.label = new St.Label({ text: this.app.get_name() });
        this.add(this.label, { x_fill: false });
    }

    set_size(size) {
        this.icon = this.app.create_icon_texture(size);
        this._iconBin.child = this.icon;
        this._iconBin.set_size(size, size);
    }

    vfunc_get_preferred_width(forHeight) {
        let [minWidth, ] = super.vfunc_get_preferred_width(forHeight);

        minWidth = Math.max(minWidth, forHeight);
        return [minWidth, minWidth];
    }
});

var AppSwitcher = GObject.registerClass(
class AppSwitcher extends SwitcherPopup.SwitcherList {
    _init(apps, altTabPopup) {
        super._init(true);

        this.icons = [];
        this._arrows = [];

        let windowTracker = Shell.WindowTracker.get_default();
        let settings = new Gio.Settings({ schema_id: 'org.gnome.shell.app-switcher' });

        let workspace = null;
        if (settings.get_boolean('current-workspace-only')) {
            let workspaceManager = global.workspace_manager;

            workspace = workspaceManager.get_active_workspace();
        }

        let allWindows = global.display.get_tab_list(Meta.TabList.NORMAL, workspace);

        // Construct the AppIcons, add to the popup
        for (let i = 0; i < apps.length; i++) {
            let appIcon = new AppIcon(apps[i]);
            // Cache the window list now; we don't handle dynamic changes here,
            // and we don't want to be continually retrieving it
            appIcon.cachedWindows = allWindows.filter(
                w => windowTracker.get_window_app (w) == appIcon.app
            );
            if (appIcon.cachedWindows.length > 0)
                this._addIcon(appIcon);
        }

        this._curApp = -1;
        this._altTabPopup = altTabPopup;
        this._mouseTimeOutId = 0;

        this.connect('destroy', this._onDestroy.bind(this));
    }

    _onDestroy() {
        if (this._mouseTimeOutId != 0)
            Mainloop.source_remove(this._mouseTimeOutId);

        this.icons.forEach(icon => {
            icon.app.disconnect(icon._stateChangedId);
        });
    }

    _setIconSize() {
        let j = 0;
        while(this._items.length > 1 && this._items[j].style_class != 'item-box') {
                j++;
        }
        let themeNode = this._items[j].get_theme_node();

        let iconPadding = themeNode.get_horizontal_padding();
        let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
        let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
        let iconSpacing = iconNaturalHeight + iconPadding + iconBorder;
        let totalSpacing = this._list.spacing * (this._items.length - 1);

        // We just assume the whole screen here due to weirdness happing with the passed width
        let primary = Main.layoutManager.primaryMonitor;
        let parentPadding = this.get_parent().get_theme_node().get_horizontal_padding();
        let availWidth = primary.width - parentPadding - this.get_theme_node().get_horizontal_padding();

        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        let iconSizes = baseIconSizes.map(s => s * scaleFactor);
        let iconSize = baseIconSizes[0];

        if (this._items.length > 1) {
            for(let i =  0; i < baseIconSizes.length; i++) {
                iconSize = baseIconSizes[i];
                let height = iconSizes[i] + iconSpacing;
                let w = height * this._items.length + totalSpacing;
                if (w <= availWidth)
                    break;
            }
        }

        this._iconSize = iconSize;

        for(let i = 0; i < this.icons.length; i++) {
            if (this.icons[i].icon != null)
                break;
            this.icons[i].set_size(iconSize);
        }
    }

    vfunc_get_preferred_height(forWidth) {
        this._setIconSize();
        return super.vfunc_get_preferred_height(forWidth);
    }

    vfunc_allocate(box, flags) {
        // Allocate the main list items
        super.vfunc_allocate(box, flags);

        let contentBox = this.get_theme_node().get_content_box(box);

        let arrowHeight = Math.floor(this.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
        let arrowWidth = arrowHeight * 2;

        // Now allocate each arrow underneath its item
        let childBox = new Clutter.ActorBox();
        for (let i = 0; i < this._items.length; i++) {
            let itemBox = this._items[i].allocation;
            childBox.x1 = contentBox.x1 + Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
            childBox.x2 = childBox.x1 + arrowWidth;
            childBox.y1 = contentBox.y1 + itemBox.y2 + arrowHeight;
            childBox.y2 = childBox.y1 + arrowHeight;
            this._arrows[i].allocate(childBox, flags);
        }
    }

    // We override SwitcherList's _onItemEnter method to delay
    // activation when the thumbnail list is open
    _onItemEnter(index) {
        if (this._mouseTimeOutId != 0)
            Mainloop.source_remove(this._mouseTimeOutId);
        if (this._altTabPopup.thumbnailsVisible) {
            this._mouseTimeOutId = Mainloop.timeout_add(APP_ICON_HOVER_TIMEOUT,
                                                        () => {
                                                            this._enterItem(index);
                                                            this._mouseTimeOutId = 0;
                                                            return GLib.SOURCE_REMOVE;
                                                        });
            GLib.Source.set_name_by_id(this._mouseTimeOutId, '[gnome-shell] this._enterItem');
        } else
           this._itemEntered(index);
    }

    _enterItem(index) {
        let [x, y, mask] = global.get_pointer();
        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
        if (this._items[index].contains(pickedActor))
            this._itemEntered(index);
    }

    // We override SwitcherList's highlight() method to also deal with
    // the AppSwitcher->ThumbnailList arrows. Apps with only 1 window
    // will hide their arrows by default, but show them when their
    // thumbnails are visible (ie, when the app icon is supposed to be
    // in justOutline mode). Apps with multiple windows will normally
    // show a dim arrow, but show a bright arrow when they are
    // highlighted.
    highlight(n, justOutline) {
        if (this.icons[this._curApp]) {
            if (this.icons[this._curApp].cachedWindows.length == 1)
                this._arrows[this._curApp].hide();
            else
                this._arrows[this._curApp].remove_style_pseudo_class('highlighted');
        }

        super.highlight(n, justOutline);
        this._curApp = n;

        if (this._curApp != -1) {
            if (justOutline && this.icons[this._curApp].cachedWindows.length == 1)
                this._arrows[this._curApp].show();
            else
                this._arrows[this._curApp].add_style_pseudo_class('highlighted');
        }
    }

    _addIcon(appIcon) {
        this.icons.push(appIcon);
        let item = this.addItem(appIcon, appIcon.label);

        appIcon._stateChangedId = appIcon.app.connect('notify::state', app => {
            if (app.state != Shell.AppState.RUNNING)
                this._removeIcon(app);
        });

        let n = this._arrows.length;
        let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
        arrow.connect('repaint', () => { SwitcherPopup.drawArrow(arrow, St.Side.BOTTOM); });
        this.add_actor(arrow);
        this._arrows.push(arrow);

        if (appIcon.cachedWindows.length == 1)
            arrow.hide();
        else
            item.add_accessible_state (Atk.StateType.EXPANDABLE);
    }

    _removeIcon(app) {
        let index = this.icons.findIndex(icon => {
            return icon.app == app;
        });
        if (index === -1)
            return;

        this.icons.splice(index, 1);
        this.removeItem(index);
    }
});

var ThumbnailList = GObject.registerClass(
class ThumbnailList extends SwitcherPopup.SwitcherList {
    _init(windows) {
        super._init(false);

        this._labels = new Array();
        this._thumbnailBins = new Array();
        this._clones = new Array();
        this._windows = windows;

        for (let i = 0; i < windows.length; i++) {
            let box = new St.BoxLayout({ style_class: 'thumbnail-box',
                                         vertical: true });

            let bin = new St.Bin({ style_class: 'thumbnail' });

            box.add_actor(bin);
            this._thumbnailBins.push(bin);

            let title = windows[i].get_title();
            if (title) {
                let name = new St.Label({ text: title });
                // St.Label doesn't support text-align so use a Bin
                let bin = new St.Bin({ x_align: St.Align.MIDDLE });
                this._labels.push(bin);
                bin.add_actor(name);
                box.add_actor(bin);

                this.addItem(box, name);
            } else {
                this.addItem(box, null);
            }

        }

        this.connect('destroy', this._onDestroy.bind(this));
    }

    addClones(availHeight) {
        if (!this._thumbnailBins.length)
            return;
        let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
        totalPadding += this.get_theme_node().get_horizontal_padding() + this.get_theme_node().get_vertical_padding();
        let [labelMinHeight, labelNaturalHeight] = this._labels[0].get_preferred_height(-1);
        let spacing = this._items[0].child.get_theme_node().get_length('spacing');
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        let thumbnailSize = THUMBNAIL_DEFAULT_SIZE * scaleFactor;

        availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, thumbnailSize);
        let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.get_theme_node().get_vertical_padding() - spacing;
        binHeight = Math.min(thumbnailSize, binHeight);

        for (let i = 0; i < this._thumbnailBins.length; i++) {
            let mutterWindow = this._windows[i].get_compositor_private();
            if (!mutterWindow)
                continue;

            let clone = _createWindowClone(mutterWindow, thumbnailSize);
            this._thumbnailBins[i].set_height(binHeight);
            this._thumbnailBins[i].add_actor(clone);

            clone._destroyId = mutterWindow.connect('destroy', source => {
                this._removeThumbnail(source, clone);
            });
            this._clones.push(clone);
        }

        // Make sure we only do this once
        this._thumbnailBins = new Array();
    }

    _removeThumbnail(source, clone) {
        let index = this._clones.indexOf(clone);
        if (index === -1)
            return;

        this._clones.splice(index, 1);
        this._windows.splice(index, 1);
        this._labels.splice(index, 1);
        this.removeItem(index);

        if (this._clones.length > 0)
            this.highlight(SwitcherPopup.mod(index, this._clones.length));
        else
            this.destroy();
    }

    _onDestroy() {
        this._clones.forEach(clone => {
            if (clone.source)
                clone.source.disconnect(clone._destroyId);
        });
    }
});

var WindowIcon = GObject.registerClass(
class WindowIcon extends St.BoxLayout {
    _init(window, mode) {
        super._init({ style_class: 'alt-tab-app',
                      vertical: true });

        this.window = window;

        this._icon = new St.Widget({ layout_manager: new Clutter.BinLayout() });

        this.add(this._icon, { x_fill: false, y_fill: false } );
        this.label = new St.Label({ text: window.get_title() });

        let tracker = Shell.WindowTracker.get_default();
        this.app = tracker.get_window_app(window);

        let mutterWindow = this.window.get_compositor_private();
        let size;

        this._icon.destroy_all_children();

        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;

        switch (mode) {
            case AppIconMode.THUMBNAIL_ONLY:
                size = WINDOW_PREVIEW_SIZE;
                this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor));
                break;

            case AppIconMode.BOTH:
                size = WINDOW_PREVIEW_SIZE;
                this._icon.add_actor(_createWindowClone(mutterWindow, size * scaleFactor));

                if (this.app)
                    this._icon.add_actor(this._createAppIcon(this.app,
                                                             APP_ICON_SIZE_SMALL));
                break;

            case AppIconMode.APP_ICON_ONLY:
                size = APP_ICON_SIZE;
                this._icon.add_actor(this._createAppIcon(this.app, size));
        }

        this._icon.set_size(size * scaleFactor, size * scaleFactor);
    }

    _createAppIcon(app, size) {
        let appIcon = app ? app.create_icon_texture(size)
                          : new St.Icon({ icon_name: 'icon-missing',
                                          icon_size: size });
        appIcon.x_expand = appIcon.y_expand = true;
        appIcon.x_align = appIcon.y_align = Clutter.ActorAlign.END;

        return appIcon;
    }
});

var WindowList = GObject.registerClass(
class WindowList extends SwitcherPopup.SwitcherList {
    _init(windows, mode) {
        super._init(true);

        this._label = new St.Label({ x_align: Clutter.ActorAlign.CENTER,
                                     y_align: Clutter.ActorAlign.CENTER });
        this.add_actor(this._label);

        this.windows = windows;
        this.icons = [];

        for (let i = 0; i < windows.length; i++) {
            let win = windows[i];
            let icon = new WindowIcon(win, mode);

            this.addItem(icon, icon.label);
            this.icons.push(icon);

            icon._unmanagedSignalId = icon.window.connect('unmanaged', (window) => {
                this._removeWindow(window)
            });
        }

        this.connect('destroy', this._onDestroy.bind(this));
    }

    _onDestroy() {
        this.icons.forEach(icon => {
            icon.window.disconnect(icon._unmanagedSignalId);
        });
    }

    vfunc_get_preferred_height(forWidth) {
        let [minHeight, natHeight] = super.vfunc_get_preferred_height(forWidth);

        let spacing = this.get_theme_node().get_padding(St.Side.BOTTOM);
        let [labelMin, labelNat] = this._label.get_preferred_height(-1);

        minHeight += labelMin + spacing;
        natHeight += labelNat + spacing;

        return [minHeight, natHeight];
    }

    vfunc_allocate(box, flags) {
        let themeNode = this.get_theme_node();
        let contentBox = themeNode.get_content_box(box);

        let childBox = new Clutter.ActorBox();
        childBox.x1 = contentBox.x1;
        childBox.x2 = contentBox.x2;
        childBox.y2 = contentBox.y2;
        childBox.y1 = childBox.y2 - this._label.height;
        this._label.allocate(childBox, flags);

        let totalLabelHeight = this._label.height + themeNode.get_padding(St.Side.BOTTOM)
        childBox.x1 = box.x1;
        childBox.x2 = box.x2;
        childBox.y1 = box.y1;
        childBox.y2 = box.y2 - totalLabelHeight;
        super.vfunc_allocate(childBox, flags);

        // Hooking up the parent vfunc will call this.set_allocation() with
        // the height without the label height, so call it again with the
        // correct size here.
        this.set_allocation(box, flags);
    }

    highlight(index, justOutline) {
        super.highlight(index, justOutline);

        this._label.set_text(index == -1 ? '' : this.icons[index].label.text);
    }

    _removeWindow(window) {
        let index = this.icons.findIndex(icon => {
            return icon.window == window;
        });
        if (index === -1)
            return;

        this.icons.splice(index, 1);
        this.removeItem(index);
    }
});
(uuay)location.js(2// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Shell } = imports.gi;

const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const ModalDialog = imports.ui.modalDialog;
const PermissionStore = imports.misc.permissionStore;
const Signals = imports.signals;

const { loadInterfaceXML } = imports.misc.fileUtils;

const LOCATION_SCHEMA = 'org.gnome.system.location';
const MAX_ACCURACY_LEVEL = 'max-accuracy-level';
const ENABLED = 'enabled';

const APP_PERMISSIONS_TABLE = 'gnome';
const APP_PERMISSIONS_ID = 'geolocation';

var GeoclueAccuracyLevel = {
    NONE: 0,
    COUNTRY: 1,
    CITY: 4,
    NEIGHBORHOOD: 5,
    STREET: 6,
    EXACT: 8
};

function accuracyLevelToString(accuracyLevel) {
    for (let key in GeoclueAccuracyLevel) {
        if (GeoclueAccuracyLevel[key] == accuracyLevel)
            return key;
    }

    return 'NONE';
}

var GeoclueIface = loadInterfaceXML('org.freedesktop.GeoClue2.Manager');
const GeoclueManager = Gio.DBusProxy.makeProxyWrapper(GeoclueIface);

var AgentIface = loadInterfaceXML('org.freedesktop.GeoClue2.Agent');

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._settings = new Gio.Settings({ schema_id: LOCATION_SCHEMA });
        this._settings.connect('changed::' + ENABLED,
                               this._onMaxAccuracyLevelChanged.bind(this));
        this._settings.connect('changed::' + MAX_ACCURACY_LEVEL,
                               this._onMaxAccuracyLevelChanged.bind(this));

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'find-location-symbolic';

        this._item = new PopupMenu.PopupSubMenuMenuItem('', true);
        this._item.icon.icon_name = 'find-location-symbolic';

        this._agent = Gio.DBusExportedObject.wrapJSObject(AgentIface, this);
        this._agent.export(Gio.DBus.system, '/org/freedesktop/GeoClue2/Agent');

        this._item.label.text = _("Location Enabled");
        this._onOffAction = this._item.menu.addAction(_("Disable"), this._onOnOffAction.bind(this));
        this._item.menu.addSettingsAction(_("Privacy Settings"), 'gnome-privacy-panel.desktop');

        this.menu.addMenuItem(this._item);

        this._watchId = Gio.bus_watch_name(Gio.BusType.SYSTEM,
                                           'org.freedesktop.GeoClue2',
                                           0,
                                           this._connectToGeoclue.bind(this),
                                           this._onGeoclueVanished.bind(this));
        Main.sessionMode.connect('updated', this._onSessionUpdated.bind(this));
        this._onSessionUpdated();
        this._onMaxAccuracyLevelChanged();
        this._connectToGeoclue();
        this._connectToPermissionStore();
    }

    get MaxAccuracyLevel() {
        return this._getMaxAccuracyLevel();
    }

    AuthorizeAppAsync(params, invocation) {
        let [desktopId, reqAccuracyLevel] = params;

        let authorizer = new AppAuthorizer(desktopId,
                                           reqAccuracyLevel,
                                           this._permStoreProxy,
                                           this._getMaxAccuracyLevel());

        authorizer.authorize(accuracyLevel => {
            let ret = (accuracyLevel != GeoclueAccuracyLevel.NONE);
            invocation.return_value(GLib.Variant.new('(bu)',
                                                     [ret, accuracyLevel]));
        });
    }

    _syncIndicator() {
        if (this._managerProxy == null) {
            this._indicator.visible = false;
            this._item.actor.visible = false;
            return;
        }

        this._indicator.visible = this._managerProxy.InUse;
        this._item.actor.visible = this._indicator.visible;
        this._updateMenuLabels();
    }

    _connectToGeoclue() {
        if (this._managerProxy != null || this._connecting)
            return false;

        this._connecting = true;
        new GeoclueManager(Gio.DBus.system,
                           'org.freedesktop.GeoClue2',
                           '/org/freedesktop/GeoClue2/Manager',
                           this._onManagerProxyReady.bind(this));
        return true;
    }

    _onManagerProxyReady(proxy, error) {
        if (error != null) {
            log(error.message);
            this._connecting = false;
            return;
        }

        this._managerProxy = proxy;
        this._propertiesChangedId = this._managerProxy.connect('g-properties-changed',
                                                        this._onGeocluePropsChanged.bind(this));

        this._syncIndicator();

        this._managerProxy.AddAgentRemote('gnome-shell', this._onAgentRegistered.bind(this));
    }

    _onAgentRegistered(result, error) {
        this._connecting = false;
        this._notifyMaxAccuracyLevel();

        if (error != null)
            log(error.message);
    }

    _onGeoclueVanished() {
        if (this._propertiesChangedId) {
            this._managerProxy.disconnect(this._propertiesChangedId);
            this._propertiesChangedId = 0;
        }
        this._managerProxy = null;

        this._syncIndicator();
    }

    _onOnOffAction() {
        let enabled = this._settings.get_boolean(ENABLED);
        this._settings.set_boolean(ENABLED, !enabled);
    }

    _onSessionUpdated() {
        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        this.menu.setSensitive(sensitive);
    }

    _updateMenuLabels() {
        if (this._settings.get_boolean(ENABLED)) {
            this._item.label.text = this._indicator.visible ? _("Location In Use")
                                                            : _("Location Enabled");
            this._onOffAction.label.text = _("Disable");
        } else {
            this._item.label.text = _("Location Disabled");
            this._onOffAction.label.text = _("Enable");
        }
    }

    _onMaxAccuracyLevelChanged() {
        this._updateMenuLabels();

        // Gotta ensure geoclue is up and we are registered as agent to it
        // before we emit the notify for this property change.
        if (!this._connectToGeoclue())
            this._notifyMaxAccuracyLevel();
    }

    _getMaxAccuracyLevel() {
        if (this._settings.get_boolean(ENABLED)) {
            let level = this._settings.get_string(MAX_ACCURACY_LEVEL);

            return GeoclueAccuracyLevel[level.toUpperCase()] ||
                   GeoclueAccuracyLevel.NONE;
        } else {
            return GeoclueAccuracyLevel.NONE;
        }
    }

    _notifyMaxAccuracyLevel() {
        let variant = new GLib.Variant('u', this._getMaxAccuracyLevel());
        this._agent.emit_property_changed('MaxAccuracyLevel', variant);
    }

    _onGeocluePropsChanged(proxy, properties) {
        let unpacked = properties.deep_unpack();
        if ("InUse" in unpacked)
            this._syncIndicator();
    }

    _connectToPermissionStore() {
        this._permStoreProxy = null;
        new PermissionStore.PermissionStore(this._onPermStoreProxyReady.bind(this));
    }

    _onPermStoreProxyReady(proxy, error) {
        if (error != null) {
            log(error.message);
            return;
        }

        this._permStoreProxy = proxy;
    }
};

function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

var AppAuthorizer = class {
    constructor(desktopId, reqAccuracyLevel, permStoreProxy, maxAccuracyLevel) {
        this.desktopId = desktopId;
        this.reqAccuracyLevel = reqAccuracyLevel;
        this._permStoreProxy = permStoreProxy;
        this._maxAccuracyLevel = maxAccuracyLevel;
        this._permissions = {};

        this._accuracyLevel = GeoclueAccuracyLevel.NONE;
    }

    authorize(onAuthDone) {
        this._onAuthDone = onAuthDone;

        let appSystem = Shell.AppSystem.get_default();
        this._app = appSystem.lookup_app(this.desktopId + ".desktop");
        if (this._app == null || this._permStoreProxy == null) {
            this._completeAuth();

            return;
        }

        this._permStoreProxy.LookupRemote(APP_PERMISSIONS_TABLE,
                                          APP_PERMISSIONS_ID,
                                          this._onPermLookupDone.bind(this));
    }

    _onPermLookupDone(result, error) {
        if (error != null) {
            if (error.domain == Gio.DBusError) {
                // Likely no xdg-app installed, just authorize the app
                this._accuracyLevel = this.reqAccuracyLevel;
                this._permStoreProxy = null;
                this._completeAuth();
            } else {
                // Currently xdg-app throws an error if we lookup for
                // unknown ID (which would be the case first time this code
                // runs) so we continue with user authorization as normal
                // and ID is added to the store if user says "yes".
                log(error.message);
                this._permissions = {};
                this._userAuthorizeApp();
            }

            return;
        }

        [this._permissions] = result;
        let permission = this._permissions[this.desktopId];

        if (permission == null) {
            this._userAuthorizeApp();
        } else {
            let [levelStr] = permission || ['NONE'];
            this._accuracyLevel = GeoclueAccuracyLevel[levelStr] ||
                                  GeoclueAccuracyLevel.NONE;
            this._completeAuth();
        }
    }

    _userAuthorizeApp() {
        let name = this._app.get_name();
        let appInfo = this._app.get_app_info();
        let reason = appInfo.get_locale_string("X-Geoclue-Reason");

        this._showAppAuthDialog(name, reason);
    }

    _showAppAuthDialog(name, reason) {
        this._dialog = new GeolocationDialog(name,
                                             reason,
                                             this.reqAccuracyLevel);

        let responseId = this._dialog.connect('response', (dialog, level) => {
            this._dialog.disconnect(responseId);
            this._accuracyLevel = level;
            this._completeAuth();
        });

        this._dialog.open();
    }

    _completeAuth() {
        if (this._accuracyLevel != GeoclueAccuracyLevel.NONE) {
            this._accuracyLevel = clamp(this._accuracyLevel,
                                        0,
                                        this._maxAccuracyLevel);
        }
        this._saveToPermissionStore();

        this._onAuthDone(this._accuracyLevel);
    }

    _saveToPermissionStore() {
        if (this._permStoreProxy == null)
            return;

        let levelStr = accuracyLevelToString(this._accuracyLevel);
        let dateStr = Math.round(Date.now() / 1000).toString();
        this._permissions[this.desktopId] = [levelStr, dateStr];

        let data = GLib.Variant.new('av', {});

        this._permStoreProxy.SetRemote(APP_PERMISSIONS_TABLE,
                                       true,
                                       APP_PERMISSIONS_ID,
                                       this._permissions,
                                       data,
                                       (result, error) => {
            if (error != null)
                log(error.message);
        });
    }
};

var GeolocationDialog = class extends ModalDialog.ModalDialog {
    constructor(name, subtitle, reqAccuracyLevel) {
        super({ styleClass: 'geolocation-dialog' });
        this.reqAccuracyLevel = reqAccuracyLevel;

        let icon = new Gio.ThemedIcon({ name: 'find-location-symbolic' });

        /* Translators: %s is an application name */
        let title = _("Give %s access to your location?").format(name);
        let body = _("Location access can be changed at any time from the privacy settings.");

        let contentParams = { icon, title, subtitle, body };
        let content = new Dialog.MessageDialogContent(contentParams);
        this.contentLayout.add_actor(content);

        let button = this.addButton({ label: _("Deny Access"),
                                      action: this._onDenyClicked.bind(this),
                                      key: Clutter.KEY_Escape });
        this.addButton({ label: _("Grant Access"),
                         action: this._onGrantClicked.bind(this) });

        this.setInitialKeyFocus(button);
    }

    _onGrantClicked() {
        this.emit('response', this.reqAccuracyLevel);
        this.close();
    }

    _onDenyClicked() {
        this.emit('response', GeoclueAccuracyLevel.NONE);
        this.close();
    }
};
Signals.addSignalMethods(GeolocationDialog.prototype);
(uuay)systemActions.js�Gconst { AccountsService, Clutter, Gdm, Gio, GLib, GObject, Meta } = imports.gi;

const GnomeSession = imports.misc.gnomeSession;
const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;

const { loadInterfaceXML } = imports.misc.fileUtils;

const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
const DISABLE_USER_SWITCH_KEY = 'disable-user-switching';
const DISABLE_LOCK_SCREEN_KEY = 'disable-lock-screen';
const DISABLE_LOG_OUT_KEY = 'disable-log-out';
const DISABLE_RESTART_KEY = 'disable-restart-buttons';
const ALWAYS_SHOW_LOG_OUT_KEY = 'always-show-log-out';

const SENSOR_BUS_NAME = 'net.hadess.SensorProxy';
const SENSOR_OBJECT_PATH = '/net/hadess/SensorProxy';

const SensorProxyInterface = loadInterfaceXML('net.hadess.SensorProxy');

const POWER_OFF_ACTION_ID        = 'power-off';
const LOCK_SCREEN_ACTION_ID      = 'lock-screen';
const LOGOUT_ACTION_ID           = 'logout';
const SUSPEND_ACTION_ID          = 'suspend';
const SWITCH_USER_ACTION_ID      = 'switch-user';
const LOCK_ORIENTATION_ACTION_ID = 'lock-orientation';

const SensorProxy = Gio.DBusProxy.makeProxyWrapper(SensorProxyInterface);

let _singleton = null;

function getDefault() {
    if (_singleton == null)
        _singleton = new SystemActions();

    return _singleton;
}

const SystemActions = GObject.registerClass({
    Properties: {
        'can-power-off': GObject.ParamSpec.boolean('can-power-off',
                                                   'can-power-off',
                                                   'can-power-off',
                                                   GObject.ParamFlags.READABLE,
                                                   false),
        'can-suspend': GObject.ParamSpec.boolean('can-suspend',
                                                 'can-suspend',
                                                 'can-suspend',
                                                 GObject.ParamFlags.READABLE,
                                                 false),
        'can-lock-screen': GObject.ParamSpec.boolean('can-lock-screen',
                                                     'can-lock-screen',
                                                     'can-lock-screen',
                                                     GObject.ParamFlags.READABLE,
                                                     false),
        'can-switch-user': GObject.ParamSpec.boolean('can-switch-user',
                                                     'can-switch-user',
                                                     'can-switch-user',
                                                     GObject.ParamFlags.READABLE,
                                                     false),
        'can-logout': GObject.ParamSpec.boolean('can-logout',
                                                'can-logout',
                                                'can-logout',
                                                GObject.ParamFlags.READABLE,
                                                false),
        'can-lock-orientation': GObject.ParamSpec.boolean('can-lock-orientation',
                                                          'can-lock-orientation',
                                                          'can-lock-orientation',
                                                          GObject.ParamFlags.READABLE,
                                                          false),
        'orientation-lock-icon': GObject.ParamSpec.string('orientation-lock-icon',
                                                          'orientation-lock-icon',
                                                          'orientation-lock-icon',
                                                          GObject.ParamFlags.READWRITE,
                                                          null)
    }
}, class SystemActions extends GObject.Object {
    _init() {
        super._init();

        this._canHavePowerOff = true;
        this._canHaveSuspend = true;

        this._actions = new Map();
        this._actions.set(POWER_OFF_ACTION_ID,
                          { // Translators: The name of the power-off action in search
                            name: C_("search-result", "Power Off"),
                            iconName: 'system-shutdown-symbolic',
                            // Translators: A list of keywords that match the power-off action, separated by semicolons
                            keywords: _("power off;shutdown;reboot;restart").split(';'),
                            available: false });
        this._actions.set(LOCK_SCREEN_ACTION_ID,
                          { // Translators: The name of the lock screen action in search
                            name: C_("search-result", "Lock Screen"),
                            iconName: 'system-lock-screen-symbolic',
                            // Translators: A list of keywords that match the lock screen action, separated by semicolons
                            keywords: _("lock screen").split(';'),
                            available: false });
        this._actions.set(LOGOUT_ACTION_ID,
                          { // Translators: The name of the logout action in search
                            name: C_("search-result", "Log Out"),
                            iconName: 'application-exit-symbolic',
                            // Translators: A list of keywords that match the logout action, separated by semicolons
                            keywords: _("logout;sign off").split(';'),
                            available: false });
        this._actions.set(SUSPEND_ACTION_ID,
                          { // Translators: The name of the suspend action in search
                            name: C_("search-result", "Suspend"),
                            iconName: 'media-playback-pause-symbolic',
                            // Translators: A list of keywords that match the suspend action, separated by semicolons
                            keywords: _("suspend;sleep").split(';'),
                            available: false });
        this._actions.set(SWITCH_USER_ACTION_ID,
                          { // Translators: The name of the switch user action in search
                            name: C_("search-result", "Switch User"),
                            iconName: 'system-switch-user-symbolic',
                            // Translators: A list of keywords that match the switch user action, separated by semicolons
                            keywords: _("switch user").split(';'),
                            available: false });
        this._actions.set(LOCK_ORIENTATION_ACTION_ID,
                          { // Translators: The name of the lock orientation action in search
                            name: C_("search-result", "Lock Orientation"),
                            iconName: '',
                            // Translators: A list of keywords that match the lock orientation action, separated by semicolons
                            keywords: _("lock orientation;screen;rotation").split(';'),
                            available: false });

        this._loginScreenSettings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
        this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
        this._orientationSettings = new Gio.Settings({ schema_id: 'org.gnome.settings-daemon.peripherals.touchscreen' });

        this._session = new GnomeSession.SessionManager();
        this._loginManager = LoginManager.getLoginManager();
        this._monitorManager = Meta.MonitorManager.get();

        this._userManager = AccountsService.UserManager.get_default();

        this._userManager.connect('notify::is-loaded',
                                  () => { this._updateMultiUser(); });
        this._userManager.connect('notify::has-multiple-users',
                                  () => { this._updateMultiUser(); });
        this._userManager.connect('user-added',
                                  () => { this._updateMultiUser(); });
        this._userManager.connect('user-removed',
                                  () => { this._updateMultiUser(); });

        this._lockdownSettings.connect('changed::' + DISABLE_USER_SWITCH_KEY,
                                       () => { this._updateSwitchUser(); });
        this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
                                       () => { this._updateLogout(); });
        global.settings.connect('changed::' + ALWAYS_SHOW_LOG_OUT_KEY,
                                () => { this._updateLogout(); });

        this._lockdownSettings.connect('changed::' + DISABLE_LOCK_SCREEN_KEY,
                                       () => { this._updateLockScreen(); });

        this._lockdownSettings.connect('changed::' + DISABLE_LOG_OUT_KEY,
                                       () => { this._updateHaveShutdown(); });

        this.forceUpdate();

        this._orientationSettings.connect('changed::orientation-lock',
                                          () => { this._updateOrientationLock();
                                                  this._updateOrientationLockIcon(); });
        Main.layoutManager.connect('monitors-changed',
                                   () => { this._updateOrientationLock(); });
        Gio.DBus.system.watch_name(SENSOR_BUS_NAME,
                                   Gio.BusNameWatcherFlags.NONE,
                                   () => { this._sensorProxyAppeared(); },
                                   () => {
                                       this._sensorProxy = null;
                                       this._updateOrientationLock();
                                   });
        this._updateOrientationLock();
        this._updateOrientationLockIcon();

        Main.sessionMode.connect('updated', () => { this._sessionUpdated(); });
        this._sessionUpdated();
    }

    get can_power_off() {
        return this._actions.get(POWER_OFF_ACTION_ID).available;
    }

    get can_suspend() {
        return this._actions.get(SUSPEND_ACTION_ID).available;
    }

    get can_lock_screen() {
        return this._actions.get(LOCK_SCREEN_ACTION_ID).available;
    }

    get can_switch_user() {
        return this._actions.get(SWITCH_USER_ACTION_ID).available;
    }

    get can_logout() {
        return this._actions.get(LOGOUT_ACTION_ID).available;
    }

    get can_lock_orientation() {
        return this._actions.get(LOCK_ORIENTATION_ACTION_ID).available;
    }

    get orientation_lock_icon() {
        return this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName;
    }

    _sensorProxyAppeared() {
        this._sensorProxy = new SensorProxy(Gio.DBus.system, SENSOR_BUS_NAME, SENSOR_OBJECT_PATH,
            (proxy, error)  => {
                if (error) {
                    log(error.message);
                    return;
                }
                this._sensorProxy.connect('g-properties-changed',
                                          () => { this._updateOrientationLock(); });
                this._updateOrientationLock();
            });
    }

    _updateOrientationLock() {
        let available = false;
        if (this._sensorProxy)
            available = this._sensorProxy.HasAccelerometer &&
                        this._monitorManager.get_is_builtin_display_on();

        this._actions.get(LOCK_ORIENTATION_ACTION_ID).available = available;

        this.notify('can-lock-orientation');
    }

    _updateOrientationLockIcon() {
        let locked = this._orientationSettings.get_boolean('orientation-lock');
        let iconName = locked ? 'rotation-locked-symbolic'
                              : 'rotation-allowed-symbolic';
        this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName = iconName;

        this.notify('orientation-lock-icon');
    }

    _sessionUpdated() {
        this._updateLockScreen();
        this._updatePowerOff();
        this._updateSuspend();
        this._updateMultiUser();
    }

    forceUpdate() {
        // Whether those actions are available or not depends on both lockdown
        // settings and Polkit policy - we don't get change notifications for the
        // latter, so their value may be outdated; force an update now
        this._updateHaveShutdown();
        this._updateHaveSuspend();
    }

    getMatchingActions(terms) {
        // terms is a list of strings
        terms = terms.map((term) => { return term.toLowerCase(); });

        let results = [];

        for (let [key, {available, keywords}] of this._actions)
            if (available && terms.every(t => keywords.some(k => (k.indexOf(t) >= 0))))
                results.push(key);

        return results;
    }

    getName(id) {
        return this._actions.get(id).name;
    }

    getIconName(id) {
        return this._actions.get(id).iconName;
    }

    activateAction(id) {
        switch (id) {
            case POWER_OFF_ACTION_ID:
                this.activatePowerOff();
                break;
            case LOCK_SCREEN_ACTION_ID:
                this.activateLockScreen();
                break;
            case LOGOUT_ACTION_ID:
                this.activateLogout();
                break;
            case SUSPEND_ACTION_ID:
                this.activateSuspend();
                break;
            case SWITCH_USER_ACTION_ID:
                this.activateSwitchUser();
                break;
            case LOCK_ORIENTATION_ACTION_ID:
                this.activateLockOrientation();
                break;
        }
    }

    _updateLockScreen() {
        let showLock = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        let allowLockScreen = !this._lockdownSettings.get_boolean(DISABLE_LOCK_SCREEN_KEY);
        this._actions.get(LOCK_SCREEN_ACTION_ID).available = showLock && allowLockScreen && LoginManager.canLock();
        this.notify('can-lock-screen');
    }

    _updateHaveShutdown() {
        this._session.CanShutdownRemote((result, error) => {
            if (error)
                return;

            this._canHavePowerOff = result[0];
            this._updatePowerOff();
        });
    }

    _updatePowerOff() {
        let disabled = Main.sessionMode.isLocked ||
                       (Main.sessionMode.isGreeter &&
                        this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY));
        this._actions.get(POWER_OFF_ACTION_ID).available = this._canHavePowerOff && !disabled;
        this.notify('can-power-off');
    }

    _updateHaveSuspend() {
        this._loginManager.canSuspend(
            (canSuspend, needsAuth) => {
                this._canHaveSuspend = canSuspend;
                this._suspendNeedsAuth = needsAuth;
                this._updateSuspend();
            });
    }

    _updateSuspend() {
        let disabled = (Main.sessionMode.isLocked &&
                        this._suspendNeedsAuth) ||
                       (Main.sessionMode.isGreeter &&
                        this._loginScreenSettings.get_boolean(DISABLE_RESTART_KEY));
        this._actions.get(SUSPEND_ACTION_ID).available = this._canHaveSuspend && !disabled;
        this.notify('can-suspend');
    }

    _updateMultiUser() {
        this._updateLogout();
        this._updateSwitchUser();
    }

    _updateSwitchUser() {
        let allowSwitch = !this._lockdownSettings.get_boolean(DISABLE_USER_SWITCH_KEY);
        let multiUser = this._userManager.can_switch() && this._userManager.has_multiple_users;
        let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;

        let visible = allowSwitch && multiUser && shouldShowInMode;
        this._actions.get(SWITCH_USER_ACTION_ID).available = visible;
        this.notify('can-switch-user');

        return visible;
    }

    _updateLogout() {
        let user = this._userManager.get_user(GLib.get_user_name());

        let allowLogout = !this._lockdownSettings.get_boolean(DISABLE_LOG_OUT_KEY);
        let alwaysShow = global.settings.get_boolean(ALWAYS_SHOW_LOG_OUT_KEY);
        let systemAccount = user.system_account;
        let localAccount = user.local_account;
        let multiUser = this._userManager.has_multiple_users;
        let multiSession = Gdm.get_session_ids().length > 1;
        let shouldShowInMode = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;

        let visible = allowLogout && (alwaysShow || multiUser || multiSession || systemAccount || !localAccount) && shouldShowInMode;
        this._actions.get(LOGOUT_ACTION_ID).available = visible;
        this.notify('can-logout');

        return visible;
    }

    activateLockOrientation() {
        if (!this._actions.get(LOCK_ORIENTATION_ACTION_ID).available)
            throw new Error('The lock-orientation action is not available!');

        let locked = this._orientationSettings.get_boolean('orientation-lock');
        this._orientationSettings.set_boolean('orientation-lock', !locked);
    }

    activateLockScreen() {
        if (!this._actions.get(LOCK_SCREEN_ACTION_ID).available)
            throw new Error('The lock-screen action is not available!');

        Main.screenShield.lock(true);
    }

    activateSwitchUser() {
        if (!this._actions.get(SWITCH_USER_ACTION_ID).available)
            throw new Error('The switch-user action is not available!');

        if (Main.screenShield)
            Main.screenShield.lock(false);

        Clutter.threads_add_repaint_func_full(Clutter.RepaintFlags.POST_PAINT, () => {
            Gdm.goto_login_session_sync(null);
            return false;
        });
    }

    activateLogout() {
        if (!this._actions.get(LOGOUT_ACTION_ID).available)
            throw new Error('The logout action is not available!');

        Main.overview.hide();
        this._session.LogoutRemote(0);
    }

    activatePowerOff() {
        if (!this._actions.get(POWER_OFF_ACTION_ID).available)
            throw new Error('The power-off action is not available!');

        this._session.ShutdownRemote(0);
    }

    activateSuspend() {
        if (!this._actions.get(SUSPEND_ACTION_ID).available)
            throw new Error('The suspend action is not available!');

        this._loginManager.suspend();
    }
});
(uuay)windowAttentionHandler.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Shell = imports.gi.Shell;

const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

var WindowAttentionHandler = class {
    constructor() {
        this._tracker = Shell.WindowTracker.get_default();
        this._windowDemandsAttentionId = global.display.connect('window-demands-attention',
                                                                this._onWindowDemandsAttention.bind(this));
        this._windowMarkedUrgentId = global.display.connect('window-marked-urgent',
                                                                this._onWindowDemandsAttention.bind(this));
    }

    _getTitleAndBanner(app, window) {
        let title = app.get_name();
        let banner = _("“%s” is ready").format(window.get_title());
        return [title, banner]
    }

    _onWindowDemandsAttention(display, window) {
        // We don't want to show the notification when the window is already focused,
        // because this is rather pointless.
        // Some apps (like GIMP) do things like setting the urgency hint on the
        // toolbar windows which would result into a notification even though GIMP itself is
        // focused.
        // We are just ignoring the hint on skip_taskbar windows for now.
        // (Which is the same behaviour as with metacity + panel)

        if (!window || window.has_focus() || window.is_skip_taskbar())
            return;

        let app = this._tracker.get_window_app(window);
        let source = new Source(app, window);
        Main.messageTray.add(source);

        let [title, banner] = this._getTitleAndBanner(app, window);

        let notification = new MessageTray.Notification(source, title, banner);
        notification.connect('activated', () => {
            source.open();
        });
        notification.setForFeedback(true);

        source.notify(notification);

        source.signalIDs.push(window.connect('notify::title', () => {
            let [title, banner] = this._getTitleAndBanner(app, window);
            notification.update(title, banner);
        }));
    }
};

var Source = class WindowAttentionSource extends MessageTray.Source {
    constructor(app, window) {
        super(app.get_name());

        this._window = window;
        this._app = app;

        this.signalIDs = [];
        this.signalIDs.push(this._window.connect('notify::demands-attention',
                                                 this._sync.bind(this)));
        this.signalIDs.push(this._window.connect('notify::urgent',
                                                 this._sync.bind(this)));
        this.signalIDs.push(this._window.connect('focus',
                                                 () => { this.destroy(); }));
        this.signalIDs.push(this._window.connect('unmanaged',
                                                 () => { this.destroy(); }));
    }

    _sync() {
        if (this._window.demands_attention || this._window.urgent)
            return;
        this.destroy();
    }

    _createPolicy() {
        if (this._app && this._app.get_app_info()) {
            let id = this._app.get_id().replace(/\.desktop$/,'');
            return new MessageTray.NotificationApplicationPolicy(id);
        } else {
            return new MessageTray.NotificationGenericPolicy();
        }
    }

    createIcon(size) {
        return this._app.create_icon_texture(size);
    }

    destroy(params) {
        for (let i = 0; i < this.signalIDs.length; i++)
            this._window.disconnect(this.signalIDs[i]);
        this.signalIDs = [];

        super.destroy(params);
    }

    open() {
        Main.activateWindow(this._window);
    }
};
(uuay)switchMonitor.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Meta, St } = imports.gi;

const SwitcherPopup = imports.ui.switcherPopup;

var APP_ICON_SIZE = 96;

var SwitchMonitorPopup = GObject.registerClass(
class SwitchMonitorPopup extends SwitcherPopup.SwitcherPopup {
    _init() {
        let items = [{ icon:  'view-mirror-symbolic',
                       /* Translators: this is for display mirroring i.e. cloning.
                        * Try to keep it under around 15 characters.
                        */
                       label: _('Mirror') },
                     { icon:  'video-joined-displays-symbolic',
                       /* Translators: this is for the desktop spanning displays.
                        * Try to keep it under around 15 characters.
                        */
                       label: _('Join Displays') },
                     { icon:  'video-single-display-symbolic',
                       /* Translators: this is for using only an external display.
                        * Try to keep it under around 15 characters.
                        */
                       label: _('External Only') },
                     { icon:  'computer-symbolic',
                       /* Translators: this is for using only the laptop display.
                        * Try to keep it under around 15 characters.
                        */
                       label: _('Built-in Only') }];

        super._init(items);

        this._switcherList = new SwitchMonitorSwitcher(items);
    }

    show(backward, binding, mask) {
        if (!Meta.MonitorManager.get().can_switch_config())
            return false;

        return super.show(backward, binding, mask);
    }

    _initialSelection() {
        let currentConfig = Meta.MonitorManager.get().get_switch_config();
        let selectConfig = (currentConfig + 1) % Meta.MonitorSwitchConfigType.UNKNOWN;
        this._select(selectConfig);
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.SWITCH_MONITOR)
            this._select(this._next());
        else if (keysym == Clutter.Left)
            this._select(this._previous());
        else if (keysym == Clutter.Right)
            this._select(this._next());
        else
            return Clutter.EVENT_PROPAGATE;

        return Clutter.EVENT_STOP;
    }

    _finish() {
        super._finish();

        Meta.MonitorManager.get().switch_config(this._selectedIndex);
    }
});

var SwitchMonitorSwitcher = GObject.registerClass(
class SwitchMonitorSwitcher extends SwitcherPopup.SwitcherList {
    _init(items) {
        super._init(true);

        for (let i = 0; i < items.length; i++)
            this._addIcon(items[i]);
    }

    _addIcon(item) {
        let box = new St.BoxLayout({ style_class: 'alt-tab-app',
                                     vertical: true });

        let icon = new St.Icon({ icon_name: item.icon,
                                 icon_size: APP_ICON_SIZE });
        box.add(icon, { x_fill: false, y_fill: false } );

        let text = new St.Label({ text: item.label });
        box.add(text, { x_fill: false });

        this.addItem(box, text);
    }
});
(uuay)popupMenu.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, Gio, GObject, Shell, St } = imports.gi;
const Signals = imports.signals;

const BoxPointer = imports.ui.boxpointer;
const GrabHelper = imports.ui.grabHelper;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

var Ornament = {
    NONE: 0,
    DOT: 1,
    CHECK: 2,
};

function isPopupMenuItemVisible(child) {
    if (child._delegate instanceof PopupMenuSection)
        if (child._delegate.isEmpty())
            return false;
    return child.visible;
}

/**
 * @side Side to which the arrow points.
 */
function arrowIcon(side) {
    let iconName;
    switch (side) {
        case St.Side.TOP:
            iconName = 'pan-up-symbolic';
            break;
        case St.Side.RIGHT:
            iconName = 'pan-end-symbolic';
            break;
        case St.Side.BOTTOM:
            iconName = 'pan-down-symbolic';
            break;
        case St.Side.LEFT:
            iconName = 'pan-start-symbolic';
            break;
    }

    let arrow = new St.Icon({ style_class: 'popup-menu-arrow',
                              icon_name: iconName,
                              accessible_role: Atk.Role.ARROW,
                              y_expand: true,
                              y_align: Clutter.ActorAlign.CENTER });

    return arrow;
}

var PopupBaseMenuItem = class {
    constructor(params) {
        params = Params.parse (params, { reactive: true,
                                         activate: true,
                                         hover: true,
                                         style_class: null,
                                         can_focus: true
                                       });

        this.actor = new St.BoxLayout({ style_class: 'popup-menu-item',
                                        reactive: params.reactive,
                                        track_hover: params.reactive,
                                        can_focus: params.can_focus,
                                        accessible_role: Atk.Role.MENU_ITEM });
        this.actor._delegate = this;

        this._ornament = Ornament.NONE;
        this._ornamentLabel = new St.Label({ style_class: 'popup-menu-ornament' });
        this.actor.add(this._ornamentLabel);

        this._parent = null;
        this.active = false;
        this._activatable = params.reactive && params.activate;
        this._sensitive = true;

        if (!this._activatable)
            this.actor.add_style_class_name('popup-inactive-menu-item');

        if (params.style_class)
            this.actor.add_style_class_name(params.style_class);

        if (this._activatable) {
            this.actor.connect('button-press-event', this._onButtonPressEvent.bind(this));
            this.actor.connect('button-release-event', this._onButtonReleaseEvent.bind(this));
            this.actor.connect('touch-event', this._onTouchEvent.bind(this));
            this.actor.connect('key-press-event', this._onKeyPressEvent.bind(this));
        }
        if (params.reactive && params.hover)
            this.actor.connect('notify::hover', this._onHoverChanged.bind(this));

        this.actor.connect('key-focus-in', this._onKeyFocusIn.bind(this));
        this.actor.connect('key-focus-out', this._onKeyFocusOut.bind(this));
        this.actor.connect('destroy', this._onDestroy.bind(this));
    }

    _getTopMenu() {
        if (this._parent)
            return this._parent._getTopMenu();
        else
            return this;
    }

    _setParent(parent) {
        this._parent = parent;
    }

    _onButtonPressEvent(actor, event) {
        // This is the CSS active state
        this.actor.add_style_pseudo_class ('active');
        return Clutter.EVENT_PROPAGATE;
    }

    _onButtonReleaseEvent(actor, event) {
        this.actor.remove_style_pseudo_class ('active');
        this.activate(event);
        return Clutter.EVENT_STOP;
    }

    _onTouchEvent(actor, event) {
        if (event.type() == Clutter.EventType.TOUCH_END) {
            this.actor.remove_style_pseudo_class ('active');
            this.activate(event);
            return Clutter.EVENT_STOP;
        } else if (event.type() == Clutter.EventType.TOUCH_BEGIN) {
            // This is the CSS active state
            this.actor.add_style_pseudo_class ('active');
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onKeyPressEvent(actor, event) {
        let state = event.get_state();

        // if user has a modifier down (except capslock and numlock)
        // then don't handle the key press here
        state &= ~Clutter.ModifierType.LOCK_MASK;
        state &= ~Clutter.ModifierType.MOD2_MASK;
        state &= Clutter.ModifierType.MODIFIER_MASK;

        if (state)
            return Clutter.EVENT_PROPAGATE;

        let symbol = event.get_key_symbol();
        if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
            this.activate(event);
            return Clutter.EVENT_STOP;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onKeyFocusIn(actor) {
        this.setActive(true);
    }

    _onKeyFocusOut(actor) {
        this.setActive(false);
    }

    _onHoverChanged(actor) {
        this.setActive(actor.hover);
    }

    activate(event) {
        this.emit('activate', event);
    }

    setActive(active) {
        let activeChanged = active != this.active;
        if (activeChanged) {
            this.active = active;
            if (active) {
                this.actor.add_style_class_name('selected');
                if (this.actor.can_focus)
                    this.actor.grab_key_focus();
            } else {
                this.actor.remove_style_class_name('selected');
                // Remove the CSS active state if the user press the button and
                // while holding moves to another menu item, so we don't paint all items.
                // The correct behaviour would be to set the new item with the CSS
                // active state as well, but button-press-event is not trigered,
                // so we should track it in our own, which would involve some work
                // in the container
                this.actor.remove_style_pseudo_class ('active');
            }
            this.emit('active-changed', active);
        }
    }

    syncSensitive() {
        let sensitive = this.getSensitive();
        this.actor.reactive = sensitive;
        this.actor.can_focus = sensitive;
        this.emit('sensitive-changed');
        return sensitive;
    }

    getSensitive() {
        let parentSensitive = this._parent ? this._parent.getSensitive() : true;
        return this._activatable && this._sensitive && parentSensitive;
    }

    setSensitive(sensitive) {
        if (this._sensitive == sensitive)
            return;

        this._sensitive = sensitive;
        this.syncSensitive();
    }

    destroy() {
        this.actor.destroy();
    }

    _onDestroy() {
        this.emit('destroy');
    }

    setOrnament(ornament) {
        if (ornament == this._ornament)
            return;

        this._ornament = ornament;

        if (ornament == Ornament.DOT) {
            this._ornamentLabel.text = '\u2022';
            this.actor.add_accessible_state(Atk.StateType.CHECKED);
        } else if (ornament == Ornament.CHECK) {
            this._ornamentLabel.text = '\u2713';
            this.actor.add_accessible_state(Atk.StateType.CHECKED);
        } else if (ornament == Ornament.NONE) {
            this._ornamentLabel.text = '';
            this.actor.remove_accessible_state(Atk.StateType.CHECKED);
        }
    }
};
Signals.addSignalMethods(PopupBaseMenuItem.prototype);

var PopupMenuItem = class extends PopupBaseMenuItem {
    constructor(text, params) {
        super(params);

        this.label = new St.Label({ text: text });
        this.actor.add_child(this.label);
        this.actor.label_actor = this.label
    }
};

var PopupSeparatorMenuItem = class extends PopupBaseMenuItem {
    constructor(text) {
        super({ reactive: false,
                can_focus: false});

        this.label = new St.Label({ text: text || '' });
        this.actor.add(this.label);
        this.actor.label_actor = this.label;

        this.label.connect('notify::text',
                           this._syncVisibility.bind(this));
        this._syncVisibility();

        this._separator = new St.Widget({ style_class: 'popup-separator-menu-item',
                                          y_expand: true,
                                          y_align: Clutter.ActorAlign.CENTER });
        this.actor.add(this._separator, { expand: true });
    }

    _syncVisibility() {
        this.label.visible = this.label.text != '';
    }
};

var Switch = class {
    constructor(state) {
        this.actor = new St.Bin({ style_class: 'toggle-switch',
                                  accessible_role: Atk.Role.CHECK_BOX,
                                  can_focus: true });
        this.setToggleState(state);
    }

    setToggleState(state) {
        if (state)
            this.actor.add_style_pseudo_class('checked');
        else
            this.actor.remove_style_pseudo_class('checked');
        this.state = state;
    }

    toggle() {
        this.setToggleState(!this.state);
    }
};

var PopupSwitchMenuItem = class extends PopupBaseMenuItem {
    constructor(text, active, params) {
        super(params);

        this.label = new St.Label({ text: text });
        this._switch = new Switch(active);

        this.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM;
        this.checkAccessibleState();
        this.actor.label_actor = this.label;

        this.actor.add_child(this.label);

        this._statusBin = new St.Bin({ x_align: St.Align.END });
        this.actor.add(this._statusBin, { expand: true, x_align: St.Align.END });

        this._statusLabel = new St.Label({ text: '',
                                           style_class: 'popup-status-menu-item'
                                         });
        this._statusBin.child = this._switch.actor;
    }

    setStatus(text) {
        if (text != null) {
            this._statusLabel.text = text;
            this._statusBin.child = this._statusLabel;
            this.actor.reactive = false;
            this.actor.accessible_role = Atk.Role.MENU_ITEM;
        } else {
            this._statusBin.child = this._switch.actor;
            this.actor.reactive = true;
            this.actor.accessible_role = Atk.Role.CHECK_MENU_ITEM;
        }
        this.checkAccessibleState();
    }

    activate(event) {
        if (this._switch.actor.mapped) {
            this.toggle();
        }

        // we allow pressing space to toggle the switch
        // without closing the menu
        if (event.type() == Clutter.EventType.KEY_PRESS &&
            event.get_key_symbol() == Clutter.KEY_space)
            return;

        super.activate(event);
    }

    toggle() {
        this._switch.toggle();
        this.emit('toggled', this._switch.state);
        this.checkAccessibleState();
    }

    get state() {
        return this._switch.state;
    }

    setToggleState(state) {
        this._switch.setToggleState(state);
        this.checkAccessibleState();
    }

    checkAccessibleState() {
        switch (this.actor.accessible_role) {
        case Atk.Role.CHECK_MENU_ITEM:
            if (this._switch.state)
                this.actor.add_accessible_state (Atk.StateType.CHECKED);
            else
                this.actor.remove_accessible_state (Atk.StateType.CHECKED);
            break;
        default:
            this.actor.remove_accessible_state (Atk.StateType.CHECKED);
        }
    }
};

var PopupImageMenuItem = class extends PopupBaseMenuItem {
    constructor(text, icon, params) {
        super(params);

        this._icon = new St.Icon({ style_class: 'popup-menu-icon',
                                   x_align: Clutter.ActorAlign.END });
        this.actor.add_child(this._icon);
        this.label = new St.Label({ text: text });
        this.actor.add_child(this.label);
        this.actor.label_actor = this.label;

        this.setIcon(icon);
    }

    setIcon(icon) {
        // The 'icon' parameter can be either a Gio.Icon or a string.
        if (icon instanceof GObject.Object && GObject.type_is_a(icon, Gio.Icon))
            this._icon.gicon = icon;
        else
            this._icon.icon_name = icon;
    }
};

var PopupMenuBase = class {
    constructor(sourceActor, styleClass) {
        if (new.target === PopupMenuBase)
            throw new TypeError('Cannot instantiate abstract class ' + new.target.name);

        this.sourceActor = sourceActor;
        this._parent = null;

        if (styleClass !== undefined) {
            this.box = new St.BoxLayout({ style_class: styleClass,
                                          vertical: true });
        } else {
            this.box = new St.BoxLayout({ vertical: true });
        }
        this.length = 0;

        this.isOpen = false;

        // If set, we don't send events (including crossing events) to the source actor
        // for the menu which causes its prelight state to freeze
        this.blockSourceEvents = false;

        this._activeMenuItem = null;
        this._settingsActions = { };

        this._sensitive = true;

        this._sessionUpdatedId = Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
    }

    _getTopMenu() {
        if (this._parent)
            return this._parent._getTopMenu();
        else
            return this;
    }

    _setParent(parent) {
        this._parent = parent;
    }

    getSensitive() {
        let parentSensitive = this._parent ? this._parent.getSensitive() : true;
        return this._sensitive && parentSensitive;
    }

    setSensitive(sensitive) {
        this._sensitive = sensitive;
        this.emit('sensitive-changed');
    }

    _sessionUpdated() {
        this._setSettingsVisibility(Main.sessionMode.allowSettings);
        this.close();
    }

    addAction(title, callback, icon) {
        let menuItem;
        if (icon != undefined)
            menuItem = new PopupImageMenuItem(title, icon);
        else
            menuItem = new PopupMenuItem(title);

        this.addMenuItem(menuItem);
        menuItem.connect('activate', (menuItem, event) => {
            callback(event);
        });

        return menuItem;
    }

    addSettingsAction(title, desktopFile) {
        let menuItem = this.addAction(title, () => {
            let app = Shell.AppSystem.get_default().lookup_app(desktopFile);

            if (!app) {
                log('Settings panel for desktop file ' + desktopFile + ' could not be loaded!');
                return;
            }

            Main.overview.hide();
            app.activate();
        });

        menuItem.actor.visible = Main.sessionMode.allowSettings;
        this._settingsActions[desktopFile] = menuItem;

        return menuItem;
    }

    _setSettingsVisibility(visible) {
        for (let id in this._settingsActions) {
            let item = this._settingsActions[id];
            item.actor.visible = visible;
        }
    }

    isEmpty() {
        let hasVisibleChildren = this.box.get_children().some(child => {
            if (child._delegate instanceof PopupSeparatorMenuItem)
                return false;
            return isPopupMenuItemVisible(child);
        });

        return !hasVisibleChildren;
    }

    itemActivated(animate) {
        if (animate == undefined)
            animate = BoxPointer.PopupAnimation.FULL;

        this._getTopMenu().close(animate);
    }

    _subMenuActiveChanged(submenu, submenuItem) {
        if (this._activeMenuItem && this._activeMenuItem != submenuItem)
            this._activeMenuItem.setActive(false);
        this._activeMenuItem = submenuItem;
        this.emit('active-changed', submenuItem);
    }

    _connectItemSignals(menuItem) {
        menuItem._activeChangeId = menuItem.connect('active-changed', (menuItem, active) => {
            if (active && this._activeMenuItem != menuItem) {
                if (this._activeMenuItem)
                    this._activeMenuItem.setActive(false);
                this._activeMenuItem = menuItem;
                this.emit('active-changed', menuItem);
            } else if (!active && this._activeMenuItem == menuItem) {
                this._activeMenuItem = null;
                this.emit('active-changed', null);
            }
        });
        menuItem._sensitiveChangeId = menuItem.connect('sensitive-changed', () => {
            let sensitive = menuItem.getSensitive();
            if (!sensitive && this._activeMenuItem == menuItem) {
                if (!this.actor.navigate_focus(menuItem.actor,
                                               St.DirectionType.TAB_FORWARD,
                                               true))
                    this.actor.grab_key_focus();
            } else if (sensitive && this._activeMenuItem == null) {
                if (global.stage.get_key_focus() == this.actor)
                    menuItem.actor.grab_key_focus();
            }
        });
        menuItem._activateId = menuItem.connect('activate', (menuItem, event) => {
            this.emit('activate', menuItem);
            this.itemActivated(BoxPointer.PopupAnimation.FULL);
        });

        menuItem._parentSensitiveChangeId = this.connect('sensitive-changed', () => {
            menuItem.syncSensitive();
        });

        // the weird name is to avoid a conflict with some random property
        // the menuItem may have, called destroyId
        // (FIXME: in the future it may make sense to have container objects
        // like PopupMenuManager does)
        menuItem._popupMenuDestroyId = menuItem.connect('destroy', menuItem => {
            menuItem.disconnect(menuItem._popupMenuDestroyId);
            menuItem.disconnect(menuItem._activateId);
            menuItem.disconnect(menuItem._activeChangeId);
            menuItem.disconnect(menuItem._sensitiveChangeId);
            this.disconnect(menuItem._parentSensitiveChangeId);
            if (menuItem == this._activeMenuItem)
                this._activeMenuItem = null;
        });
    }

    _updateSeparatorVisibility(menuItem) {
        if (menuItem.label.text)
            return;

        let children = this.box.get_children();

        let index = children.indexOf(menuItem.actor);

        if (index < 0)
            return;

        let childBeforeIndex = index - 1;

        while (childBeforeIndex >= 0 && !isPopupMenuItemVisible(children[childBeforeIndex]))
            childBeforeIndex--;

        if (childBeforeIndex < 0
            || children[childBeforeIndex]._delegate instanceof PopupSeparatorMenuItem) {
            menuItem.actor.hide();
            return;
        }

        let childAfterIndex = index + 1;

        while (childAfterIndex < children.length && !isPopupMenuItemVisible(children[childAfterIndex]))
            childAfterIndex++;

        if (childAfterIndex >= children.length
            || children[childAfterIndex]._delegate instanceof PopupSeparatorMenuItem) {
            menuItem.actor.hide();
            return;
        }

        menuItem.actor.show();
    }

    moveMenuItem(menuItem, position) {
        let items = this._getMenuItems();
        let i = 0;

        while (i < items.length && position > 0) {
                if (items[i] != menuItem)
                        position--;
                i++;
        }

        if (i < items.length) {
                if (items[i] != menuItem)
                        this.box.set_child_below_sibling(menuItem.actor, items[i].actor);
        } else {
                this.box.set_child_above_sibling(menuItem.actor, null);
        }
    }

    addMenuItem(menuItem, position) {
        let before_item = null;
        if (position == undefined) {
            this.box.add(menuItem.actor);
        } else {
            let items = this._getMenuItems();
            if (position < items.length) {
                before_item = items[position].actor;
                this.box.insert_child_below(menuItem.actor, before_item);
            } else {
                this.box.add(menuItem.actor);
            }
        }

        if (menuItem instanceof PopupMenuSection) {
            let activeChangeId = menuItem.connect('active-changed', this._subMenuActiveChanged.bind(this));

            let parentOpenStateChangedId = this.connect('open-state-changed', (self, open) => {
                if (open)
                    menuItem.open();
                else
                    menuItem.close();
            });
            let parentClosingId = this.connect('menu-closed', () => {
                menuItem.emit('menu-closed');
            });
            let subMenuSensitiveChangedId = this.connect('sensitive-changed', () => {
                menuItem.emit('sensitive-changed');
            });

            menuItem.connect('destroy', () => {
                menuItem.disconnect(activeChangeId);
                this.disconnect(subMenuSensitiveChangedId);
                this.disconnect(parentOpenStateChangedId);
                this.disconnect(parentClosingId);
                this.length--;
            });
        } else if (menuItem instanceof PopupSubMenuMenuItem) {
            if (before_item == null)
                this.box.add(menuItem.menu.actor);
            else
                this.box.insert_child_below(menuItem.menu.actor, before_item);

            this._connectItemSignals(menuItem);
            let subMenuActiveChangeId = menuItem.menu.connect('active-changed', this._subMenuActiveChanged.bind(this));
            let closingId = this.connect('menu-closed', () => {
                menuItem.menu.close(BoxPointer.PopupAnimation.NONE);
            });

            menuItem.connect('destroy', () => {
                menuItem.menu.disconnect(subMenuActiveChangeId);
                this.disconnect(closingId);
            });
        } else if (menuItem instanceof PopupSeparatorMenuItem) {
            this._connectItemSignals(menuItem);

            // updateSeparatorVisibility needs to get called any time the
            // separator's adjacent siblings change visibility or position.
            // open-state-changed isn't exactly that, but doing it in more
            // precise ways would require a lot more bookkeeping.
            let openStateChangeId = this.connect('open-state-changed', () => {
                this._updateSeparatorVisibility(menuItem);
            });
            let destroyId = menuItem.connect('destroy', () => {
                this.disconnect(openStateChangeId);
                menuItem.disconnect(destroyId);
            });
        } else if (menuItem instanceof PopupBaseMenuItem)
            this._connectItemSignals(menuItem);
        else
            throw TypeError("Invalid argument to PopupMenuBase.addMenuItem()");

        menuItem._setParent(this);

        this.length++;
    }

    _getMenuItems() {
        const children = this.box.get_children().filter(a => a._delegate !== undefined);
        return children.map(a => a._delegate).filter(item => {
            return item instanceof PopupBaseMenuItem || item instanceof PopupMenuSection;
        });
    }

    get firstMenuItem() {
        let items = this._getMenuItems();
        if (items.length)
            return items[0];
        else
            return null;
    }

    get numMenuItems() {
        return this._getMenuItems().length;
    }

    removeAll() {
        let children = this._getMenuItems();
        for (let i = 0; i < children.length; i++) {
            let item = children[i];
            item.destroy();
        }
    }

    toggle() {
        if (this.isOpen)
            this.close(BoxPointer.PopupAnimation.FULL);
        else
            this.open(BoxPointer.PopupAnimation.FULL);
    }

    destroy() {
        this.close();
        this.removeAll();
        this.actor.destroy();

        this.emit('destroy');

        Main.sessionMode.disconnect(this._sessionUpdatedId);
        this._sessionUpdatedId = 0;
    }
};
Signals.addSignalMethods(PopupMenuBase.prototype);

var PopupMenu = class extends PopupMenuBase {
    constructor(sourceActor, arrowAlignment, arrowSide) {
        super(sourceActor, 'popup-menu-content');

        this._arrowAlignment = arrowAlignment;
        this._arrowSide = arrowSide;

        this._boxPointer = new BoxPointer.BoxPointer(arrowSide,
                                                     { x_fill: true,
                                                       y_fill: true,
                                                       x_align: St.Align.START });
        this.actor = this._boxPointer;
        this.actor._delegate = this;
        this.actor.style_class = 'popup-menu-boxpointer';

        this._boxPointer.bin.set_child(this.box);
        this.actor.add_style_class_name('popup-menu');

        global.focus_manager.add_group(this.actor);
        this.actor.reactive = true;

        if (this.sourceActor)
            this._keyPressId = this.sourceActor.connect('key-press-event',
                                                        this._onKeyPress.bind(this));

        this._openedSubMenu = null;
    }

    _setOpenedSubMenu(submenu) {
        if (this._openedSubMenu)
            this._openedSubMenu.close(true);

        this._openedSubMenu = submenu;
    }

    _onKeyPress(actor, event) {
        // Disable toggling the menu by keyboard
        // when it cannot be toggled by pointer
        if (!actor.reactive)
            return Clutter.EVENT_PROPAGATE;

        let navKey;
        switch (this._boxPointer.arrowSide) {
            case St.Side.TOP:
                navKey = Clutter.KEY_Down;
                break;
            case St.Side.BOTTOM:
                navKey = Clutter.KEY_Up;
                break;
            case St.Side.LEFT:
                navKey = Clutter.KEY_Right;
                break;
            case St.Side.RIGHT:
                navKey = Clutter.KEY_Left;
                break;
        }

        let state = event.get_state();

        // if user has a modifier down (except capslock and numlock)
        // then don't handle the key press here
        state &= ~Clutter.ModifierType.LOCK_MASK;
        state &= ~Clutter.ModifierType.MOD2_MASK;
        state &= Clutter.ModifierType.MODIFIER_MASK;

        if (state)
            return Clutter.EVENT_PROPAGATE;

        let symbol = event.get_key_symbol();
        if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
            this.toggle();
            return Clutter.EVENT_STOP;
        } else if (symbol == Clutter.KEY_Escape && this.isOpen) {
            this.close();
            return Clutter.EVENT_STOP;
        } else if (symbol == navKey) {
            if (!this.isOpen)
                this.toggle();
            this.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
            return Clutter.EVENT_STOP;
        } else
            return Clutter.EVENT_PROPAGATE;
    }

    setArrowOrigin(origin) {
        this._boxPointer.setArrowOrigin(origin);
    }

    setSourceAlignment(alignment) {
        this._boxPointer.setSourceAlignment(alignment);
    }

    open(animate) {
        if (this.isOpen)
            return;

        if (this.isEmpty())
            return;

        this.isOpen = true;

        this._boxPointer.setPosition(this.sourceActor, this._arrowAlignment);
        this._boxPointer.open(animate);

        this.actor.raise_top();

        this.emit('open-state-changed', true);
    }

    close(animate) {
        if (this._activeMenuItem)
            this._activeMenuItem.setActive(false);

        if (this._boxPointer.actor.visible) {
            this._boxPointer.close(animate, () => {
                this.emit('menu-closed');
            });
        }

        if (!this.isOpen)
            return;

        this.isOpen = false;
        this.emit('open-state-changed', false);
    }

    destroy() {
        if (this._keyPressId)
            this.sourceActor.disconnect(this._keyPressId);
        super.destroy();
    }
};

var PopupDummyMenu = class {
    constructor(sourceActor) {
        this.sourceActor = sourceActor;
        this.actor = sourceActor;
        this.actor._delegate = this;
    }

    getSensitive() {
        return true;
    }

    open() { this.emit('open-state-changed', true); }
    close() { this.emit('open-state-changed', false); }
    toggle() {}
    destroy() {
        this.emit('destroy');
    }
};
Signals.addSignalMethods(PopupDummyMenu.prototype);

var PopupSubMenu = class extends PopupMenuBase {
    constructor(sourceActor, sourceArrow) {
        super(sourceActor);

        this._arrow = sourceArrow;

        // Since a function of a submenu might be to provide a "More.." expander
        // with long content, we make it scrollable - the scrollbar will only take
        // effect if a CSS max-height is set on the top menu.
        this.actor = new St.ScrollView({ style_class: 'popup-sub-menu',
                                         hscrollbar_policy: St.PolicyType.NEVER,
                                         vscrollbar_policy: St.PolicyType.NEVER });

        this.actor.add_actor(this.box);
        this.actor._delegate = this;
        this.actor.clip_to_allocation = true;
        this.actor.connect('key-press-event', this._onKeyPressEvent.bind(this));
        this.actor.hide();
    }

    _needsScrollbar() {
        let topMenu = this._getTopMenu();
        let [topMinHeight, topNaturalHeight] = topMenu.actor.get_preferred_height(-1);
        let topThemeNode = topMenu.actor.get_theme_node();

        let topMaxHeight = topThemeNode.get_max_height();
        return topMaxHeight >= 0 && topNaturalHeight >= topMaxHeight;
    }

    getSensitive() {
        return this._sensitive && this.sourceActor._delegate.getSensitive();
    }

    open(animate) {
        if (this.isOpen)
            return;

        if (this.isEmpty())
            return;

        this.isOpen = true;
        this.emit('open-state-changed', true);

        this.actor.show();

        let needsScrollbar = this._needsScrollbar();

        // St.ScrollView always requests space horizontally for a possible vertical
        // scrollbar if in AUTOMATIC mode. Doing better would require implementation
        // of width-for-height in St.BoxLayout and St.ScrollView. This looks bad
        // when we *don't* need it, so turn off the scrollbar when that's true.
        // Dynamic changes in whether we need it aren't handled properly.
        this.actor.vscrollbar_policy =
            needsScrollbar ? St.PolicyType.AUTOMATIC : St.PolicyType.NEVER;

        if (needsScrollbar)
            this.actor.add_style_pseudo_class('scrolled');
        else
            this.actor.remove_style_pseudo_class('scrolled');

        // It looks funny if we animate with a scrollbar (at what point is
        // the scrollbar added?) so just skip that case
        if (animate && needsScrollbar)
            animate = false;

        let targetAngle = this.actor.text_direction == Clutter.TextDirection.RTL ? -90 : 90;

        if (animate) {
            let [minHeight, naturalHeight] = this.actor.get_preferred_height(-1);
            this.actor.height = 0;
            this.actor._arrowRotation = this._arrow.rotation_angle_z;
            Tweener.addTween(this.actor,
                             { _arrowRotation: targetAngle,
                               height: naturalHeight,
                               time: 0.25,
                               onUpdateScope: this,
                               onUpdate() {
                                   this._arrow.rotation_angle_z = this.actor._arrowRotation;
                               },
                               onCompleteScope: this,
                               onComplete() {
                                   this.actor.set_height(-1);
                               }
                             });
        } else {
            this._arrow.rotation_angle_z = targetAngle;
        }
    }

    close(animate) {
        if (!this.isOpen)
            return;

        this.isOpen = false;
        this.emit('open-state-changed', false);

        if (this._activeMenuItem)
            this._activeMenuItem.setActive(false);

        if (animate && this._needsScrollbar())
            animate = false;

        if (animate) {
            this.actor._arrowRotation = this._arrow.rotation_angle_z;
            Tweener.addTween(this.actor,
                             { _arrowRotation: 0,
                               height: 0,
                               time: 0.25,
                               onUpdateScope: this,
                               onUpdate() {
                                   this._arrow.rotation_angle_z = this.actor._arrowRotation;
                               },
                               onCompleteScope: this,
                               onComplete() {
                                   this.actor.hide();
                                   this.actor.set_height(-1);
                               },
                             });
        } else {
            this._arrow.rotation_angle_z = 0;
            this.actor.hide();
        }
    }

    _onKeyPressEvent(actor, event) {
        // Move focus back to parent menu if the user types Left.

        if (this.isOpen && event.get_key_symbol() == Clutter.KEY_Left) {
            this.close(BoxPointer.PopupAnimation.FULL);
            this.sourceActor._delegate.setActive(true);
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }
};

/**
 * PopupMenuSection:
 *
 * A section of a PopupMenu which is handled like a submenu
 * (you can add and remove items, you can destroy it, you
 * can add it to another menu), but is completely transparent
 * to the user
 */
var PopupMenuSection = class extends PopupMenuBase {
    constructor() {
        super();

        this.actor = this.box;
        this.actor._delegate = this;
        this.isOpen = true;
    }

    // deliberately ignore any attempt to open() or close(), but emit the
    // corresponding signal so children can still pick it up
    open() { this.emit('open-state-changed', true); }
    close() { this.emit('open-state-changed', false); }
};

var PopupSubMenuMenuItem = class extends PopupBaseMenuItem {
    constructor(text, wantIcon) {
        super();

        this.actor.add_style_class_name('popup-submenu-menu-item');

        if (wantIcon) {
            this.icon = new St.Icon({ style_class: 'popup-menu-icon' });
            this.actor.add_child(this.icon);
        }

        this.label = new St.Label({ text: text,
                                    y_expand: true,
                                    y_align: Clutter.ActorAlign.CENTER });
        this.actor.add_child(this.label);
        this.actor.label_actor = this.label;

        let expander = new St.Bin({ style_class: 'popup-menu-item-expander' });
        this.actor.add(expander, { expand: true });

        this._triangle = arrowIcon(St.Side.RIGHT);
        this._triangle.pivot_point = new Clutter.Point({ x: 0.5, y: 0.6 });

        this._triangleBin = new St.Widget({ y_expand: true,
                                            y_align: Clutter.ActorAlign.CENTER });
        this._triangleBin.add_child(this._triangle);

        this.actor.add_child(this._triangleBin);
        this.actor.add_accessible_state (Atk.StateType.EXPANDABLE);

        this.menu = new PopupSubMenu(this.actor, this._triangle);
        this.menu.connect('open-state-changed', this._subMenuOpenStateChanged.bind(this));
    }

    _setParent(parent) {
        super._setParent(parent);
        this.menu._setParent(parent);
    }

    syncSensitive() {
        let sensitive = super.syncSensitive();
        this._triangle.visible = sensitive;
        if (!sensitive)
            this.menu.close(false);
    }

    _subMenuOpenStateChanged(menu, open) {
        if (open) {
            this.actor.add_style_pseudo_class('open');
            this._getTopMenu()._setOpenedSubMenu(this.menu);
            this.actor.add_accessible_state (Atk.StateType.EXPANDED);
            this.actor.add_style_pseudo_class('checked');
        } else {
            this.actor.remove_style_pseudo_class('open');
            this._getTopMenu()._setOpenedSubMenu(null);
            this.actor.remove_accessible_state (Atk.StateType.EXPANDED);
            this.actor.remove_style_pseudo_class('checked');
        }
    }

    destroy() {
        this.menu.destroy();

        super.destroy();
    }

    setSubmenuShown(open) {
        if (open)
            this.menu.open(BoxPointer.PopupAnimation.FULL);
        else
            this.menu.close(BoxPointer.PopupAnimation.FULL);
    }

    _setOpenState(open) {
        this.setSubmenuShown(open);
    }

    _getOpenState() {
        return this.menu.isOpen;
    }

    _onKeyPressEvent(actor, event) {
        let symbol = event.get_key_symbol();

        if (symbol == Clutter.KEY_Right) {
            this._setOpenState(true);
            this.menu.actor.navigate_focus(null, St.DirectionType.DOWN, false);
            return Clutter.EVENT_STOP;
        } else if (symbol == Clutter.KEY_Left && this._getOpenState()) {
            this._setOpenState(false);
            return Clutter.EVENT_STOP;
        }

        return super._onKeyPressEvent(actor, event);
    }

    activate(event) {
        this._setOpenState(true);
    }

    _onButtonReleaseEvent(actor) {
        // Since we override the parent, we need to manage what the parent does
        // with the active style class
        this.actor.remove_style_pseudo_class ('active');
        this._setOpenState(!this._getOpenState());
        return Clutter.EVENT_PROPAGATE;
    }

    _onTouchEvent(actor, event) {
        if (event.type() == Clutter.EventType.TOUCH_END) {
            // Since we override the parent, we need to manage what the parent does
            // with the active style class
            this.actor.remove_style_pseudo_class ('active');
            this._setOpenState(!this._getOpenState());
        }
        return Clutter.EVENT_PROPAGATE;
    }
};

/* Basic implementation of a menu manager.
 * Call addMenu to add menus
 */
var PopupMenuManager = class {
    constructor(owner, grabParams) {
        grabParams = Params.parse(grabParams,
                                  { actionMode: Shell.ActionMode.POPUP });
        this._owner = owner;
        this._grabHelper = new GrabHelper.GrabHelper(owner.actor, grabParams);
        this._menus = [];
    }

    addMenu(menu, position) {
        if (this._findMenu(menu) > -1)
            return;

        let menudata = {
            menu:              menu,
            openStateChangeId: menu.connect('open-state-changed', this._onMenuOpenState.bind(this)),
            destroyId:         menu.connect('destroy', this._onMenuDestroy.bind(this)),
            enterId:           0,
            focusInId:         0
        };

        let source = menu.sourceActor;
        if (source) {
            if (!menu.blockSourceEvents)
                this._grabHelper.addActor(source);
            menudata.enterId = source.connect('enter-event',
                () => this._onMenuSourceEnter(menu));
            menudata.focusInId = source.connect('key-focus-in', () => {
                this._onMenuSourceEnter(menu);
            });
        }

        if (position == undefined)
            this._menus.push(menudata);
        else
            this._menus.splice(position, 0, menudata);
    }

    removeMenu(menu) {
        if (menu == this.activeMenu)
            this._closeMenu(false, menu);

        let position = this._findMenu(menu);
        if (position == -1) // not a menu we manage
            return;

        let menudata = this._menus[position];
        menu.disconnect(menudata.openStateChangeId);
        menu.disconnect(menudata.destroyId);

        if (menudata.enterId)
            menu.sourceActor.disconnect(menudata.enterId);
        if (menudata.focusInId)
            menu.sourceActor.disconnect(menudata.focusInId);

        if (menu.sourceActor)
            this._grabHelper.removeActor(menu.sourceActor);
        this._menus.splice(position, 1);
    }

    get activeMenu() {
        let firstGrab = this._grabHelper.grabStack[0];
        if (firstGrab)
            return firstGrab.actor._delegate;
        else
            return null;
    }

    ignoreRelease() {
        return this._grabHelper.ignoreRelease();
    }

    _onMenuOpenState(menu, open) {
        if (open) {
            if (this.activeMenu)
                this.activeMenu.close(BoxPointer.PopupAnimation.FADE);
            this._grabHelper.grab({ actor: menu.actor, focus: menu.sourceActor,
                                    onUngrab: isUser => {
                                        this._closeMenu(isUser, menu);
                                    } });
        } else {
            this._grabHelper.ungrab({ actor: menu.actor });
        }
    }

    _changeMenu(newMenu) {
        newMenu.open(this.activeMenu ? BoxPointer.PopupAnimation.FADE
                                     : BoxPointer.PopupAnimation.FULL);
    }

    _onMenuSourceEnter(menu) {
        if (!this._grabHelper.grabbed)
            return Clutter.EVENT_PROPAGATE;

        if (this._grabHelper.isActorGrabbed(menu.actor))
            return Clutter.EVENT_PROPAGATE;

        this._changeMenu(menu);
        return Clutter.EVENT_PROPAGATE;
    }

    _onMenuDestroy(menu) {
        this.removeMenu(menu);
    }

    _findMenu(item) {
        for (let i = 0; i < this._menus.length; i++) {
            let menudata = this._menus[i];
            if (item == menudata.menu)
                return i;
        }
        return -1;
    }

    _closeMenu(isUser, menu) {
        // If this isn't a user action, we called close()
        // on the BoxPointer ourselves, so we shouldn't
        // reanimate.
        if (isUser)
            menu.close(BoxPointer.PopupAnimation.FULL);
    }
};
(uuay)smartcardManager.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;
const Signals = imports.signals;

const ObjectManager = imports.misc.objectManager;

const SmartcardTokenIface = `
<node>
<interface name="org.gnome.SettingsDaemon.Smartcard.Token">
  <property name="Name" type="s" access="read"/>
  <property name="Driver" type="o" access="read"/>
  <property name="IsInserted" type="b" access="read"/>
  <property name="UsedToLogin" type="b" access="read"/>
</interface>
</node>`;

let _smartcardManager = null;

function getSmartcardManager() {
    if (_smartcardManager == null)
        _smartcardManager = new SmartcardManager();

    return _smartcardManager;
}

var SmartcardManager = class {
    constructor() {
        this._objectManager = new ObjectManager.ObjectManager({ connection: Gio.DBus.session,
                                                                name: "org.gnome.SettingsDaemon.Smartcard",
                                                                objectPath: '/org/gnome/SettingsDaemon/Smartcard',
                                                                knownInterfaces: [ SmartcardTokenIface ],
                                                                onLoaded: this._onLoaded.bind(this) });
        this._insertedTokens = {};
        this._loginToken = null;
    }

    _onLoaded() {
        let tokens = this._objectManager.getProxiesForInterface('org.gnome.SettingsDaemon.Smartcard.Token');

        for (let i = 0; i < tokens.length; i++)
            this._addToken(tokens[i]);

        this._objectManager.connect('interface-added', (objectManager, interfaceName, proxy) => {
            if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token')
                this._addToken(proxy);
        });

        this._objectManager.connect('interface-removed', (objectManager, interfaceName, proxy) => {
            if (interfaceName == 'org.gnome.SettingsDaemon.Smartcard.Token')
                this._removeToken(proxy);
        });
    }

    _updateToken(token) {
        let objectPath = token.get_object_path();

        delete this._insertedTokens[objectPath];

        if (token.IsInserted)
            this._insertedTokens[objectPath] = token;

        if (token.UsedToLogin)
            this._loginToken = token;
    }

    _addToken(token) {
        this._updateToken(token);

        token.connect('g-properties-changed', (proxy, properties) => {
            if ('IsInserted' in properties.deep_unpack()) {
                this._updateToken(token);

                if (token.IsInserted) {
                    this.emit('smartcard-inserted', token);
                } else {
                    this.emit('smartcard-removed', token);
                }
            }
        });

        // Emit a smartcard-inserted at startup if it's already plugged in
        if (token.IsInserted)
            this.emit('smartcard-inserted', token);
    }

    _removeToken(token) {
        let objectPath = token.get_object_path();

        if (this._insertedTokens[objectPath] == token) {
            delete this._insertedTokens[objectPath];
            this.emit('smartcard-removed', token);
        }

        if (this._loginToken == token)
            this._loginToken = null;

        token.disconnectAll();
    }

    hasInsertedTokens() {
        return Object.keys(this._insertedTokens).length > 0;
    }

    hasInsertedLoginToken() {
        if (!this._loginToken)
            return false;

        if (!this._loginToken.IsInserted)
            return false;

        return true;
    }

    loggedInWithToken() {
        if (this._loginToken)
            return true;

        return false;
    }

};
Signals.addSignalMethods(SmartcardManager.prototype);
(uuay)magnifierDBus.js�.// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;
const Main = imports.ui.main;

const { loadInterfaceXML } = imports.misc.fileUtils;

const MAG_SERVICE_PATH = '/org/gnome/Magnifier';
const ZOOM_SERVICE_PATH = '/org/gnome/Magnifier/ZoomRegion';

// Subset of gnome-mag's Magnifier dbus interface -- to be expanded.  See:
// http://git.gnome.org/browse/gnome-mag/tree/xml/...Magnifier.xml
const MagnifierIface = loadInterfaceXML('org.gnome.Magnifier');

// Subset of gnome-mag's ZoomRegion dbus interface -- to be expanded.  See:
// http://git.gnome.org/browse/gnome-mag/tree/xml/...ZoomRegion.xml
const ZoomRegionIface = loadInterfaceXML('org.gnome.Magnifier.ZoomRegion');

// For making unique ZoomRegion DBus proxy object paths of the form:
// '/org/gnome/Magnifier/ZoomRegion/zoomer0',
// '/org/gnome/Magnifier/ZoomRegion/zoomer1', etc.
let _zoomRegionInstanceCount = 0;

var ShellMagnifier = class ShellMagnifier {
    constructor() {
        this._zoomers = {};

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(MagnifierIface, this);
        this._dbusImpl.export(Gio.DBus.session, MAG_SERVICE_PATH);
    }

    /**
     * setActive:
     * @activate:   Boolean to activate or de-activate the magnifier.
     */
    setActive(activate) {
        Main.magnifier.setActive(activate);
    }

    /**
     * isActive:
     * @return  Whether the magnifier is active (boolean).
     */
    isActive() {
        return Main.magnifier.isActive();
    }

    /**
     * showCursor:
     * Show the system mouse pointer.
     */
    showCursor() {
        Main.magnifier.showSystemCursor();
    }

    /**
     * hideCursor:
     * Hide the system mouse pointer.
     */
    hideCursor() {
        Main.magnifier.hideSystemCursor();
    }

    /**
     * createZoomRegion:
     * Create a new ZoomRegion and return its object path.
     * @xMagFactor:     The power to set horizontal magnification of the
     *                  ZoomRegion.  A value of 1.0 means no magnification.  A
     *                  value of 2.0 doubles the size.
     * @yMagFactor:     The power to set the vertical magnification of the
     *                  ZoomRegion.
     * @roi             Array of integers defining the region of the
     *                  screen/desktop to magnify.  The array has the form
     *                  [left, top, right, bottom].
     * @viewPort        Array of integers, [left, top, right, bottom] that defines
     *                  the position of the ZoomRegion on screen.
     *
     * FIXME: The arguments here are redundant, since the width and height of
     *   the ROI are determined by the viewport and magnification factors.
     *   We ignore the passed in width and height.
     *
     * @return          The newly created ZoomRegion.
     */
    createZoomRegion(xMagFactor, yMagFactor, roi, viewPort) {
        let ROI = { x: roi[0], y: roi[1], width: roi[2] - roi[0], height: roi[3] - roi[1] };
        let viewBox = { x: viewPort[0], y: viewPort[1], width: viewPort[2] - viewPort[0], height: viewPort[3] - viewPort[1] };
        let realZoomRegion = Main.magnifier.createZoomRegion(xMagFactor, yMagFactor, ROI, viewBox);
        let objectPath = ZOOM_SERVICE_PATH + '/zoomer' + _zoomRegionInstanceCount;
        _zoomRegionInstanceCount++;

        let zoomRegionProxy = new ShellMagnifierZoomRegion(objectPath, realZoomRegion);
        let proxyAndZoomRegion = {};
        proxyAndZoomRegion.proxy = zoomRegionProxy;
        proxyAndZoomRegion.zoomRegion = realZoomRegion;
        this._zoomers[objectPath] = proxyAndZoomRegion;
        return objectPath;
    }

    /**
     * addZoomRegion:
     * Append the given ZoomRegion to the magnifier's list of ZoomRegions.
     * @zoomerObjectPath:   The object path for the zoom region proxy.
     */
    addZoomRegion(zoomerObjectPath) {
        let proxyAndZoomRegion = this._zoomers[zoomerObjectPath];
        if (proxyAndZoomRegion && proxyAndZoomRegion.zoomRegion) {
            Main.magnifier.addZoomRegion(proxyAndZoomRegion.zoomRegion);
            return true;
        }
        else
            return false;
    }

    /**
     * getZoomRegions:
     * Return a list of ZoomRegion object paths for this Magnifier.
     * @return:     The Magnifier's zoom region list as an array of DBus object
     *              paths.
     */
    getZoomRegions() {
        // There may be more ZoomRegions in the magnifier itself than have
        // been added through dbus.  Make sure all of them are associated with
        // an object path and proxy.
        let zoomRegions = Main.magnifier.getZoomRegions();
        let objectPaths = [];
        let thoseZoomers = this._zoomers;
        zoomRegions.forEach ((aZoomRegion, index, array) => {
            let found = false;
            for (let objectPath in thoseZoomers) {
                let proxyAndZoomRegion = thoseZoomers[objectPath];
                if (proxyAndZoomRegion.zoomRegion === aZoomRegion) {
                    objectPaths.push(objectPath);
                    found = true;
                    break;
                }
            }
            if (!found) {
                // Got a ZoomRegion with no DBus proxy, make one.
                let newPath =  ZOOM_SERVICE_PATH + '/zoomer' + _zoomRegionInstanceCount;
                _zoomRegionInstanceCount++;
                let zoomRegionProxy = new ShellMagnifierZoomRegion(newPath, aZoomRegion);
                let proxyAndZoomer = {};
                proxyAndZoomer.proxy = zoomRegionProxy;
                proxyAndZoomer.zoomRegion = aZoomRegion;
                thoseZoomers[newPath] = proxyAndZoomer;
                objectPaths.push(newPath);
            }
        });
        return objectPaths;
    }

    /**
     * clearAllZoomRegions:
     * Remove all the zoom regions from this Magnfier's ZoomRegion list.
     */
    clearAllZoomRegions() {
        Main.magnifier.clearAllZoomRegions();
        for (let objectPath in this._zoomers) {
            let proxyAndZoomer = this._zoomers[objectPath];
            proxyAndZoomer.proxy.destroy();
            proxyAndZoomer.proxy = null;
            proxyAndZoomer.zoomRegion = null;
            delete this._zoomers[objectPath];
        }
        this._zoomers = {};
    }

    /**
     * fullScreenCapable:
     * Consult if the Magnifier can magnify in full-screen mode.
     * @return  Always return true.
     */
    fullScreenCapable() {
        return true;
    }

    /**
     * setCrosswireSize:
     * Set the crosswire size of all ZoomRegions.
     * @size:   The thickness of each line in the cross wire.
     */
     setCrosswireSize(size) {
        Main.magnifier.setCrosshairsThickness(size);
     }

    /**
     * getCrosswireSize:
     * Get the crosswire size of all ZoomRegions.
     * @return:   The thickness of each line in the cross wire.
     */
     getCrosswireSize() {
        return Main.magnifier.getCrosshairsThickness();
     }

    /**
     * setCrosswireLength:
     * Set the crosswire length of all zoom-regions..
     * @size:   The length of each line in the cross wire.
     */
     setCrosswireLength(length) {
        Main.magnifier.setCrosshairsLength(length);
     }

    /**
     * setCrosswireSize:
     * Set the crosswire size of all zoom-regions.
     * @size:   The thickness of each line in the cross wire.
     */
     getCrosswireLength() {
        return Main.magnifier.getCrosshairsLength();
     }

    /**
     * setCrosswireClip:
     * Set if the crosswire will be clipped by the cursor image..
     * @clip:   Flag to indicate whether to clip the crosswire.
     */
     setCrosswireClip(clip) {
        Main.magnifier.setCrosshairsClip(clip);
     }

    /**
     * getCrosswireClip:
     * Get the crosswire clip value.
     * @return:   Whether the crosswire is clipped by the cursor image.
     */
     getCrosswireClip() {
        return Main.magnifier.getCrosshairsClip();
     }

    /**
     * setCrosswireColor:
     * Set the crosswire color of all ZoomRegions.
     * @color:   Unsigned int of the form rrggbbaa.
     */
     setCrosswireColor(color) {
        Main.magnifier.setCrosshairsColor('#%08x'.format(color));
     }

    /**
     * getCrosswireClip:
     * Get the crosswire color of all ZoomRegions.
     * @return:   The crosswire color as an unsigned int in the form rrggbbaa.
     */
     getCrosswireColor() {
        let colorString = Main.magnifier.getCrosshairsColor();
        // Drop the leading '#'.
        return parseInt(colorString.slice(1), 16);
     }
};

/**
 * ShellMagnifierZoomRegion:
 * Object that implements the DBus ZoomRegion interface.
 * @zoomerObjectPath:   String that is the path to a DBus ZoomRegion.
 * @zoomRegion:         The actual zoom region associated with the object path.
 */
var ShellMagnifierZoomRegion = class ShellMagnifierZoomRegion {
    constructor(zoomerObjectPath, zoomRegion) {
        this._zoomRegion = zoomRegion;

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ZoomRegionIface, this);
        this._dbusImpl.export(Gio.DBus.session, zoomerObjectPath);
    }

    /**
     * setMagFactor:
     * @xMagFactor:     The power to set the horizontal magnification factor to
     *                  of the magnified view.  A value of 1.0 means no
     *                  magnification.  A value of 2.0 doubles the size.
     * @yMagFactor:     The power to set the vertical magnification factor to
     *                  of the magnified view.
     */
    setMagFactor(xMagFactor, yMagFactor) {
        this._zoomRegion.setMagFactor(xMagFactor, yMagFactor);
    }

    /**
     * getMagFactor:
     * @return  an array, [xMagFactor, yMagFactor], containing the horizontal
     *          and vertical magnification powers.  A value of 1.0 means no
     *          magnification.  A value of 2.0 means the contents are doubled
     *          in size, and so on.
     */
    getMagFactor() {
        return this._zoomRegion.getMagFactor();
    }

    /**
     * setRoi:
     * Sets the "region of interest" that the ZoomRegion is magnifying.
     * @roi     Array, [left, top, right, bottom], defining the region of the
     *          screen to magnify. The values are in screen (unmagnified)
     *          coordinate space.
     */
    setRoi(roi) {
        let roiObject = { x: roi[0], y: roi[1], width: roi[2] - roi[0], height: roi[3] - roi[1] };
        this._zoomRegion.setROI(roiObject);
    }

    /**
     * getRoi:
     * Retrieves the "region of interest" -- the rectangular bounds of that part
     * of the desktop that the magnified view is showing (x, y, width, height).
     * The bounds are given in non-magnified coordinates.
     * @return  an array, [left, top, right, bottom], representing the bounding
     *          rectangle of what is shown in the magnified view.
     */
    getRoi() {
        let roi = this._zoomRegion.getROI();
        roi[2] += roi[0];
        roi[3] += roi[1];
        return roi;
    }

    /**
     * Set the "region of interest" by centering the given screen coordinate
     * within the zoom region.
     * @x       The x-coord of the point to place at the center of the zoom region.
     * @y       The y-coord.
     * @return  Whether the shift was successful (for GS-mag, this is always
     *          true).
     */
    shiftContentsTo(x, y) {
        this._zoomRegion.scrollContentsTo(x, y);
        return true;
    }

    /**
     * moveResize
     * Sets the position and size of the ZoomRegion on screen.
     * @viewPort    Array, [left, top, right, bottom], defining the position and
     *              size on screen to place the zoom region.
     */
    moveResize(viewPort) {
        let viewRect = { x: viewPort[0], y: viewPort[1], width: viewPort[2] - viewPort[0], height: viewPort[3] - viewPort[1] };
        this._zoomRegion.setViewPort(viewRect);
    }

    destroy() {
        this._dbusImpl.unexport();
    }
};
(uuay)padOsd.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, GDesktopEnums, Gio,
        GLib, GObject, Gtk, Meta, Rsvg, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Layout = imports.ui.layout;

const { loadInterfaceXML } = imports.misc.fileUtils;

const ACTIVE_COLOR = "#729fcf";

const LTR = 0;
const RTL = 1;

const CW = 0;
const CCW = 1;

const UP = 0;
const DOWN = 1;

var PadChooser = class {
    constructor(device, groupDevices) {
        this.actor = new St.Button({ style_class: 'pad-chooser-button',
                                     toggle_mode: true,
                                     x_fill: false,
                                     y_fill: false,
                                     x_align: St.Align.MIDDLE,
                                     y_align: St.Align.MIDDLE });
        this.currentDevice = device;
        this._padChooserMenu = null;

        let arrow = new St.Icon({ style_class: 'popup-menu-arrow',
                                  icon_name: 'pan-down-symbolic',
                                  accessible_role: Atk.Role.ARROW });
        this.actor.set_child(arrow);
        this._ensureMenu(groupDevices);

        this.actor.connect('destroy', this._onDestroy.bind(this));
        this.actor.connect('clicked', actor => {
            if (actor.get_checked()) {
                if (this._padChooserMenu != null)
                    this._padChooserMenu.open(true);
                else
                    this.set_checked(false);
            } else {
                this._padChooserMenu.close(true);
            }
        });
    }

    _ensureMenu(devices) {
        this._padChooserMenu =  new PopupMenu.PopupMenu(this.actor, 0.5, St.Side.TOP);
        this._padChooserMenu.connect('menu-closed', () => {
            this.actor.set_checked(false);
        });
        this._padChooserMenu.actor.hide();
        Main.uiGroup.add_actor(this._padChooserMenu.actor);

        for (let i = 0; i < devices.length; i++) {
            let device = devices[i];
            if (device == this.currentDevice)
                continue;

            this._padChooserMenu.addAction(device.get_device_name(), () => {
                this.emit('pad-selected', device);
            });
        }
    }

    _onDestroy() {
        this._padChooserMenu.destroy();
    }

    update(devices) {
        if (this._padChooserMenu)
            this._padChooserMenu.actor.destroy();
        this.actor.set_checked(false);
        this._ensureMenu(devices);
    }

    destroy() {
        this.actor.destroy();
    }
};
Signals.addSignalMethods(PadChooser.prototype);

var KeybindingEntry = class {
    constructor() {
        this.actor = new St.Entry({ hint_text: _("New shortcut…"),
                                    style: 'width: 10em' });
        this.actor.connect('captured-event', this._onCapturedEvent.bind(this));
    }

    _onCapturedEvent(actor, event) {
        if (event.type() != Clutter.EventType.KEY_PRESS)
            return Clutter.EVENT_PROPAGATE;

        let str = Gtk.accelerator_name_with_keycode(null,
                                                    event.get_key_symbol(),
                                                    event.get_key_code(),
                                                    event.get_state());
        this.actor.set_text(str);
        this.emit('keybinding-edited', str);
        return Clutter.EVENT_STOP;
    }
};
Signals.addSignalMethods(KeybindingEntry.prototype);

var ActionComboBox = class {
    constructor() {
        this.actor = new St.Button({ style_class: 'button' });
        this.actor.connect('clicked', this._onButtonClicked.bind(this));
        this.actor.set_toggle_mode(true);

        let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL,
                                                spacing: 6 });
        let box = new St.Widget({ layout_manager: boxLayout });
        this.actor.set_child(box);

        this._label = new St.Label({ style_class: 'combo-box-label' });
        box.add_child(this._label)

        let arrow = new St.Icon({ style_class: 'popup-menu-arrow',
                                  icon_name: 'pan-down-symbolic',
                                  accessible_role: Atk.Role.ARROW,
                                  y_expand: true,
                                  y_align: Clutter.ActorAlign.CENTER });
        box.add_child(arrow);

        this._editMenu = new PopupMenu.PopupMenu(this.actor, 0, St.Side.TOP);
        this._editMenu.connect('menu-closed', () => {
            this.actor.set_checked(false);
        });
        this._editMenu.actor.hide();
        Main.uiGroup.add_actor(this._editMenu.actor);

        this._actionLabels = new Map();
        this._actionLabels.set(GDesktopEnums.PadButtonAction.NONE, _("Application defined"));
        this._actionLabels.set(GDesktopEnums.PadButtonAction.HELP, _("Show on-screen help"));
        this._actionLabels.set(GDesktopEnums.PadButtonAction.SWITCH_MONITOR, _("Switch monitor"));
        this._actionLabels.set(GDesktopEnums.PadButtonAction.KEYBINDING, _("Assign keystroke"));

        this._buttonItems = [];

        for (let [action, label] of this._actionLabels.entries()) {
            let selectedAction = action;
            let item = this._editMenu.addAction(label, () => {
                this._onActionSelected(selectedAction);
            });

            /* These actions only apply to pad buttons */
            if (selectedAction == GDesktopEnums.PadButtonAction.HELP ||
                selectedAction == GDesktopEnums.PadButtonAction.SWITCH_MONITOR)
                this._buttonItems.push(item);
        }

        this.setAction(GDesktopEnums.PadButtonAction.NONE);
    }

    _onActionSelected(action) {
        this.setAction(action);
        this.popdown();
        this.emit('action-selected', action);
    }

    setAction(action) {
        this._label.set_text(this._actionLabels.get(action));
    }

    popup() {
        this._editMenu.open(true);
    }

    popdown() {
        this._editMenu.close(true);
    }

    _onButtonClicked() {
        if (this.actor.get_checked())
            this.popup();
        else
            this.popdown();
    }

    setButtonActionsActive(active) {
        this._buttonItems.forEach(item => { item.setSensitive(active); });
    }
};
Signals.addSignalMethods(ActionComboBox.prototype);

var ActionEditor = class {
    constructor() {
        let boxLayout = new Clutter.BoxLayout({ orientation: Clutter.Orientation.HORIZONTAL,
                                                spacing: 12 });

        this.actor = new St.Widget({ layout_manager: boxLayout });

        this._actionComboBox = new ActionComboBox();
        this._actionComboBox.connect('action-selected', this._onActionSelected.bind(this));
        this.actor.add_actor(this._actionComboBox.actor);

        this._keybindingEdit = new KeybindingEntry();
        this._keybindingEdit.connect('keybinding-edited', this._onKeybindingEdited.bind(this));
        this.actor.add_actor(this._keybindingEdit.actor);

        this._doneButton = new St.Button({ label: _("Done"),
                                           style_class: 'button',
                                           x_expand: false});
        this._doneButton.connect('clicked', this._onEditingDone.bind(this));
        this.actor.add_actor(this._doneButton);
    }

    _updateKeybindingEntryState() {
        if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING) {
            this._keybindingEdit.actor.set_text(this._currentKeybinding);
            this._keybindingEdit.actor.show();
            this._keybindingEdit.actor.grab_key_focus();
        } else {
            this._keybindingEdit.actor.hide();
        }
    }

    setSettings(settings, action) {
        this._buttonSettings = settings;

        this._currentAction = this._buttonSettings.get_enum('action');
        this._currentKeybinding = this._buttonSettings.get_string('keybinding');
        this._actionComboBox.setAction(this._currentAction);
        this._updateKeybindingEntryState();

        let isButton = (action == Meta.PadActionType.BUTTON);
        this._actionComboBox.setButtonActionsActive(isButton);
    }

    close() {
        this._actionComboBox.popdown();
        this.actor.hide();
    }

    _onKeybindingEdited(entry, keybinding) {
        this._currentKeybinding = keybinding;
    }

    _onActionSelected(menu, action) {
        this._currentAction = action;
        this._updateKeybindingEntryState();
    }

    _storeSettings() {
        if (!this._buttonSettings)
            return;

        let keybinding = null;

        if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING)
            keybinding = this._currentKeybinding;

        this._buttonSettings.set_enum('action', this._currentAction);

        if (keybinding)
            this._buttonSettings.set_string('keybinding', keybinding);
        else
            this._buttonSettings.reset('keybinding');
    }

    _onEditingDone() {
        this._storeSettings();
        this.close();
        this.emit('done');
    }
};
Signals.addSignalMethods(ActionEditor.prototype);

var PadDiagram = GObject.registerClass({
    Properties: { 'left-handed': GObject.ParamSpec.boolean('left-handed',
                                                           'left-handed', 'Left handed',
                                                           GObject.ParamFlags.READWRITE |
                                                           GObject.ParamFlags.CONSTRUCT_ONLY,
                                                           false),
                  'image': GObject.ParamSpec.string('image', 'image', 'Image',
                                                    GObject.ParamFlags.READWRITE |
                                                    GObject.ParamFlags.CONSTRUCT_ONLY,
                                                    null),
                  'editor-actor': GObject.ParamSpec.object('editor-actor',
                                                           'editor-actor',
                                                           'Editor actor',
                                                           GObject.ParamFlags.READWRITE |
                                                           GObject.ParamFlags.CONSTRUCT_ONLY,
                                                           Clutter.Actor.$gtype) },
}, class PadDiagram extends St.DrawingArea {
    _init(params) {
        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/pad-osd.css');
        let [success, css, etag] = file.load_contents(null);
        if (css instanceof Uint8Array)
            css = imports.byteArray.toString(css);
        this._curEdited = null;
        this._prevEdited = null;
        this._css = css;
        this._labels = [];
        this._activeButtons = [];
        super._init(params);
    }

    get left_handed() {
        return this._leftHanded;
    }

    set left_handed(leftHanded) {
        this._leftHanded = leftHanded;
    }

    get image() {
        return this._imagePath;
    }

    set image(imagePath) {
        let originalHandle = Rsvg.Handle.new_from_file(imagePath);
        let dimensions = originalHandle.get_dimensions();
        this._imageWidth = dimensions.width;
        this._imageHeight = dimensions.height;

        this._imagePath = imagePath;
        this._handle = this._composeStyledDiagram();
    }

    get editor_actor() {
        return this._editorActor;
    }

    set editor_actor(actor) {
        actor.hide();
        this._editorActor = actor;
        this.add_actor(actor);
    }

    _wrappingSvgHeader() {
        return ('<?xml version="1.0" encoding="UTF-8" standalone="no"?>' +
                '<svg version="1.1" xmlns="http://www.w3.org/2000/svg" ' +
                'xmlns:xi="http://www.w3.org/2001/XInclude" ' +
                'width="' + this._imageWidth + '" height="' + this._imageHeight + '"> ' +
                '<style type="text/css">');
    }

    _wrappingSvgFooter() {
        return ('</style>' +
                '<xi:include href="' + this._imagePath + '" />' +
                '</svg>');
    }

    _cssString() {
        let css = this._css;

        for (let i = 0; i < this._activeButtons.length; i++) {
            let ch = String.fromCharCode('A'.charCodeAt() + this._activeButtons[i]);
            css += ('.' + ch + ' { ' +
	            '  stroke: ' + ACTIVE_COLOR + ' !important; ' +
                    '  fill: ' + ACTIVE_COLOR + ' !important; ' +
                    '} ');
        }

        return css;
    }

    _composeStyledDiagram() {
        let svgData = '';

        if (!GLib.file_test(this._imagePath, GLib.FileTest.EXISTS))
            return null;

        svgData += this._wrappingSvgHeader();
        svgData += this._cssString();
        svgData += this._wrappingSvgFooter();

        let istream = new Gio.MemoryInputStream();
        istream.add_bytes(new GLib.Bytes(svgData));

        return Rsvg.Handle.new_from_stream_sync(istream,
                                                Gio.File.new_for_path(this._imagePath),
                                                0, null);
    }

    _updateDiagramScale() {
        if (this._handle == null)
            return;

        [this._actorWidth, this._actorHeight] = this.get_size();
        let dimensions = this._handle.get_dimensions();
        let scaleX = this._actorWidth / dimensions.width;
        let scaleY = this._actorHeight / dimensions.height;
        this._scale = Math.min(scaleX, scaleY);
    }

    _allocateChild(child, x, y, direction) {
        let [prefHeight, natHeight] = child.get_preferred_height(-1);
        let [prefWidth, natWidth] = child.get_preferred_width(natHeight);
        let childBox = new Clutter.ActorBox();

        if (direction == LTR) {
            childBox.x1 = x;
            childBox.x2 = x + natWidth;
        } else {
            childBox.x1 = x - natWidth;
            childBox.x2 = x;
        }

        childBox.y1 = y - natHeight / 2;
        childBox.y2 = y + natHeight / 2;
        child.allocate(childBox, 0);
    }

    vfunc_allocate(box, flags) {
        super.vfunc_allocate(box, flags);
        this._updateDiagramScale();

        for (let i = 0; i < this._labels.length; i++) {
            let [label, action, idx, dir] = this._labels[i];
            let [found, x, y, arrangement] = this.getLabelCoords(action, idx, dir);
            this._allocateChild(label, x, y, arrangement);
        }

        if (this._editorActor && this._curEdited) {
            let [label, action, idx, dir] = this._curEdited;
            let [found, x, y, arrangement] = this.getLabelCoords(action, idx, dir);
            this._allocateChild(this._editorActor, x, y, arrangement);
        }
    }

    vfunc_repaint() {
        if (this._handle == null)
            return;

        if (this._scale == null)
            this._updateDiagramScale();

        let [width, height] = this.get_surface_size();
        let dimensions = this._handle.get_dimensions();
        let cr = this.get_context();

        cr.save();
        cr.translate(width/2, height/2);
        cr.scale(this._scale, this._scale);
        if (this._leftHanded)
            cr.rotate(Math.PI);
        cr.translate(-dimensions.width/2, -dimensions.height/2);
        this._handle.render_cairo(cr);
        cr.restore();
        cr.$dispose();
    }

    _transformPoint(x, y) {
        if (this._handle == null || this._scale == null)
            return [x, y];

        // I miss Cairo.Matrix
        let dimensions = this._handle.get_dimensions();
        x = x * this._scale + this._actorWidth / 2 - dimensions.width / 2 * this._scale;
        y = y * this._scale + this._actorHeight / 2 - dimensions.height / 2 * this._scale;;
        return [Math.round(x), Math.round(y)];
    }

    _getItemLabelCoords(labelName, leaderName) {
        if (this._handle == null)
            return [false];

        let leaderPos, leaderSize, pos;
        let found, direction;

        [found, pos] = this._handle.get_position_sub('#' + labelName);
        if (!found)
            return [false];

        [found, leaderPos] = this._handle.get_position_sub('#' + leaderName);
        [found, leaderSize] = this._handle.get_dimensions_sub('#' + leaderName);
        if (!found)
            return [false];

        if (pos.x > leaderPos.x + leaderSize.width)
            direction = LTR;
        else
            direction = RTL;

        if (this._leftHanded) {
            direction = 1 - direction;
            pos.x = this._imageWidth - pos.x;
            pos.y = this._imageHeight - pos.y;
        }

        let [x, y] = this._transformPoint(pos.x, pos.y)

        return [true, x, y, direction];
    }

    getButtonLabelCoords(button) {
        let ch = String.fromCharCode('A'.charCodeAt() + button);
        let labelName = 'Label' + ch;
        let leaderName = 'Leader' + ch;

        return this._getItemLabelCoords(labelName, leaderName);
    }

    getRingLabelCoords(number, dir) {
        let numStr = number > 0 ? (number + 1).toString() : '';
        let dirStr = dir == CW ? 'CW' : 'CCW';
        let labelName = 'LabelRing' + numStr + dirStr;
        let leaderName = 'LeaderRing' + numStr + dirStr;

        return this._getItemLabelCoords(labelName, leaderName);
    }

    getStripLabelCoords(number, dir) {
        let numStr = number > 0 ? (number + 1).toString() : '';
        let dirStr = dir == UP ? 'Up' : 'Down';
        let labelName = 'LabelStrip' + numStr + dirStr;
        let leaderName = 'LeaderStrip' + numStr + dirStr;

        return this._getItemLabelCoords(labelName, leaderName);
    }

    getLabelCoords(action, idx, dir) {
        if (action == Meta.PadActionType.BUTTON)
            return this.getButtonLabelCoords(idx);
        else if (action == Meta.PadActionType.RING)
            return this.getRingLabelCoords(idx, dir);
        else if (action == Meta.PadActionType.STRIP)
            return this.getStripLabelCoords(idx, dir);

        return [false];
    }

    _invalidateSvg() {
        if (this._handle == null)
            return;
        this._handle = this._composeStyledDiagram();
        this.queue_repaint();
    }

    activateButton(button) {
        this._activeButtons.push(button);
        this._invalidateSvg();
    }

    deactivateButton(button) {
        for (let i = 0; i < this._activeButtons.length; i++) {
            if (this._activeButtons[i] == button)
                this._activeButtons.splice(i, 1);
        }
        this._invalidateSvg();
    }

    addLabel(label, type, idx, dir) {
        this._labels.push([label, type, idx, dir]);
        this.add_actor(label);
    }

    updateLabels(callback) {
        for (let i = 0; i < this._labels.length; i++) {
            let [label, action, idx, dir] = this._labels[i];
            let str = callback(action, idx, dir);
            label.set_text(str);
        }
    }

    _applyLabel(label, action, idx, dir, str) {
        if (str != null) {
            label.set_text(str);

            let [found, x, y, arrangement] = this.getLabelCoords(action, idx, dir);
            this._allocateChild(label, x, y, arrangement);
        }
        label.show();
    }

    stopEdition(continues, str) {
        this._editorActor.hide();

        if (this._prevEdited) {
            let [label, action, idx, dir] = this._prevEdited;
            this._applyLabel(label, action, idx, dir, str);
            this._prevEdited = null;
        }

        if (this._curEdited) {
            let [label, action, idx, dir] = this._curEdited;
            this._applyLabel(label, action, idx, dir, str);
            if (continues)
                this._prevEdited = this._curEdited;
            this._curEdited = null;
        }
    }

    startEdition(action, idx, dir) {
        let editedLabel;

        if (this._curEdited)
            return;

        for (let i = 0; i < this._labels.length; i++) {
            let [label, itemAction, itemIdx, itemDir] = this._labels[i];
            if (action == itemAction && idx == itemIdx && dir == itemDir) {
                this._curEdited = this._labels[i];
                editedLabel = label;
                break;
            }
        }

        if (this._curEdited == null)
            return;
        let [found] = this.getLabelCoords(action, idx, dir);
        if (!found)
            return;
        this._editorActor.show();
        editedLabel.hide();
    }
});

var PadOsd = class {
    constructor(padDevice, settings, imagePath, editionMode, monitorIndex) {
        this.padDevice = padDevice;
        this._groupPads = [ padDevice ];
        this._settings = settings;
        this._imagePath = imagePath;
        this._editionMode = editionMode;
        this._capturedEventId = global.stage.connect('captured-event', this._onCapturedEvent.bind(this));
        this._padChooser = null;

        let deviceManager = Clutter.DeviceManager.get_default();
        this._deviceAddedId = deviceManager.connect('device-added', (manager, device) => {
            if (device.get_device_type() == Clutter.InputDeviceType.PAD_DEVICE &&
                this.padDevice.is_grouped(device)) {
                this._groupPads.push(device);
                this._updatePadChooser();
            }
        });
        this._deviceRemovedId = deviceManager.connect('device-removed', (manager, device) => {
            // If the device is being removed, destroy the padOsd.
            if (device == this.padDevice) {
                this.destroy();
            } else if (this._groupPads.indexOf(device) != -1) {
                // Or update the pad chooser if the device belongs to
                // the same group.
                this._groupPads.splice(this._groupPads.indexOf(device), 1);
                this._updatePadChooser();

            }
        });

        deviceManager.list_devices().forEach(device => {
            if (device != this.padDevice &&
                device.get_device_type() == Clutter.InputDeviceType.PAD_DEVICE &&
                this.padDevice.is_grouped(device))
                this._groupPads.push(device);
        });

        this.actor = new St.BoxLayout({ style_class: 'pad-osd-window',
                                        x_expand: true,
                                        y_expand: true,
                                        vertical: true,
                                        reactive: true });
        this.actor.connect('destroy', this._onDestroy.bind(this));
        Main.uiGroup.add_actor(this.actor);

        this._monitorIndex = monitorIndex;
        let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
        this.actor.add_constraint(constraint);

        this._titleBox = new St.BoxLayout({ style_class: 'pad-osd-title-box',
                                            vertical: false,
                                            x_expand: false,
                                            x_align: Clutter.ActorAlign.CENTER });
        this.actor.add_actor(this._titleBox);

        let labelBox = new St.BoxLayout({ style_class: 'pad-osd-title-menu-box',
                                          vertical: true });
        this._titleBox.add_actor(labelBox);

        this._titleLabel = new St.Label({ style: 'font-side: larger; font-weight: bold;',
                                          x_align: Clutter.ActorAlign.CENTER });
        this._titleLabel.clutter_text.set_text(padDevice.get_device_name());
        labelBox.add_actor(this._titleLabel);

        this._tipLabel = new St.Label({ x_align: Clutter.ActorAlign.CENTER });
        labelBox.add_actor(this._tipLabel);

        this._updatePadChooser();

        this._actionEditor = new ActionEditor();
        this._actionEditor.connect('done', this._endActionEdition.bind(this));

        this._padDiagram = new PadDiagram({ image: this._imagePath,
                                            left_handed: settings.get_boolean('left-handed'),
                                            editor_actor: this._actionEditor.actor,
                                            x_expand: true,
                                            y_expand: true });
        this.actor.add_actor(this._padDiagram);

        // FIXME: Fix num buttons.
        let i = 0;
        for (i = 0; i < 50; i++) {
            let [found] = this._padDiagram.getButtonLabelCoords(i);
            if (!found)
                break;
            this._createLabel(Meta.PadActionType.BUTTON, i);
        }

        for (i = 0; i < padDevice.get_n_rings(); i++) {
            let [found] = this._padDiagram.getRingLabelCoords(i, CW);
            if (!found)
                break;
            this._createLabel(Meta.PadActionType.RING, i, CW);
            this._createLabel(Meta.PadActionType.RING, i, CCW);
        }

        for (i = 0; i < padDevice.get_n_strips(); i++) {
            let [found] = this._padDiagram.getStripLabelCoords(i, UP);
            if (!found)
                break;
            this._createLabel(Meta.PadActionType.STRIP, i, UP);
            this._createLabel(Meta.PadActionType.STRIP, i, DOWN);
        }

        let buttonBox = new St.Widget({ layout_manager: new Clutter.BinLayout(),
                                         x_expand: true,
                                         x_align: Clutter.ActorAlign.CENTER,
                                         y_align: Clutter.ActorAlign.CENTER });
        this.actor.add_actor(buttonBox);
        this._editButton = new St.Button({ label: _("Edit…"),
                                           style_class: 'button',
                                           x_align: Clutter.ActorAlign.CENTER,
                                           can_focus: true });
        this._editButton.connect('clicked', () => {
            this.setEditionMode(true);
        });
        buttonBox.add_actor(this._editButton);

        this._syncEditionMode();
        Main.pushModal(this.actor);
    }

    _updatePadChooser() {
        if (this._groupPads.length > 1) {
            if (this._padChooser == null) {
                this._padChooser = new PadChooser(this.padDevice, this._groupPads)
                this._padChooser.connect('pad-selected', (chooser, pad) => {
                    this._requestForOtherPad(pad);
                });
                this._titleBox.add_child(this._padChooser.actor);
            } else {
                this._padChooser.update(this._groupPads);
            }
        } else if (this._padChooser != null) {
            this._padChooser.destroy();
            this._padChooser = null;
        }
    }

    _requestForOtherPad(pad) {
        if (pad == this.padDevice ||
            this._groupPads.indexOf(pad) == -1)
            return;

        let editionMode = this._editionMode;
        this.destroy();
        global.display.request_pad_osd(pad, editionMode);
    }

    _getActionText(type, number) {
        let str = global.display.get_pad_action_label(this.padDevice, type, number);
        return str ? str : _("None");
    }

    _createLabel(type, number, dir) {
        let label = new St.Label({ text: this._getActionText(type, number) });
        this._padDiagram.addLabel(label, type, number, dir);
    }

    _updateActionLabels() {
        this._padDiagram.updateLabels(this._getActionText.bind(this));
    }

    _onCapturedEvent(actor, event) {
        let isModeSwitch =
            (event.type() == Clutter.EventType.PAD_BUTTON_PRESS ||
             event.type() == Clutter.EventType.PAD_BUTTON_RELEASE) &&
            this.padDevice.get_mode_switch_button_group(event.get_button()) >= 0;

        if (event.type() == Clutter.EventType.PAD_BUTTON_PRESS &&
            event.get_source_device() == this.padDevice) {
            this._padDiagram.activateButton(event.get_button());

            /* Buttons that switch between modes cannot be edited */
            if (this._editionMode && !isModeSwitch)
                this._startButtonActionEdition(event.get_button());
            return Clutter.EVENT_STOP;
        } else if (event.type() == Clutter.EventType.PAD_BUTTON_RELEASE &&
                   event.get_source_device() == this.padDevice) {
            this._padDiagram.deactivateButton(event.get_button());

            if (isModeSwitch) {
                this._endActionEdition();
                this._updateActionLabels();
            }
            return Clutter.EVENT_STOP;
        } else if (event.type() == Clutter.EventType.KEY_PRESS &&
                   (!this._editionMode || event.get_key_symbol() == Clutter.Escape)) {
            if (this._editedAction != null)
                this._endActionEdition();
            else
                this.destroy();
            return Clutter.EVENT_STOP;
        } else if (event.get_source_device() == this.padDevice &&
                   event.type() == Clutter.EventType.PAD_STRIP) {
            if (this._editionMode) {
                let [retval, number, mode] = event.get_pad_event_details();
                this._startStripActionEdition(number, UP, mode);
            }
        } else if (event.get_source_device() == this.padDevice &&
                   event.type() == Clutter.EventType.PAD_RING) {
            if (this._editionMode) {
                let [retval, number, mode] = event.get_pad_event_details();
                this._startRingActionEdition(number, CCW, mode);
            }
        }

        // If the event comes from another pad in the same group,
        // show the OSD for it.
        if (this._groupPads.indexOf(event.get_source_device()) != -1) {
            this._requestForOtherPad(event.get_source_device());
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _syncEditionMode() {
        this._editButton.set_reactive(!this._editionMode);
        this._editButton.save_easing_state();
        this._editButton.set_easing_duration(200);
        this._editButton.set_opacity(this._editionMode ? 128 : 255);
        this._editButton.restore_easing_state();

        let title;

        if (this._editionMode) {
            title = _("Press a button to configure");
            this._tipLabel.set_text(_("Press Esc to exit"));
        } else {
            title = this.padDevice.get_device_name();
            this._tipLabel.set_text(_("Press any key to exit"));
        }

        this._titleLabel.clutter_text.set_markup('<span size="larger"><b>' + title + '</b></span>');
    }

    _isEditedAction(type, number, dir) {
        if (!this._editedAction)
            return false;

        return (this._editedAction.type == type &&
                this._editedAction.number == number &&
                this._editedAction.dir == dir);
    }

    _followUpActionEdition(str) {
        let { type, dir, number, mode } = this._editedAction;
        let hasNextAction = (type == Meta.PadActionType.RING && dir == CCW ||
                             type == Meta.PadActionType.STRIP && dir == UP);
        if (!hasNextAction)
            return false;

        this._padDiagram.stopEdition(true, str);
        this._editedAction = null;
        if (type == Meta.PadActionType.RING)
            this._startRingActionEdition(number, CW, mode);
        else
            this._startStripActionEdition(number, DOWN, mode);

        return true;
    }

    _endActionEdition() {
        this._actionEditor.close();

        if (this._editedAction != null) {
            let str = global.display.get_pad_action_label(this.padDevice,
                                                          this._editedAction.type,
                                                          this._editedAction.number);
            if (this._followUpActionEdition(str))
                return;

            this._padDiagram.stopEdition(false, str ? str : _("None"))
            this._editedAction = null;
        }

        this._editedActionSettings = null;
    }

    _startActionEdition(key, type, number, dir, mode) {
        if (this._isEditedAction(type, number, dir))
            return;

        this._endActionEdition();
        this._editedAction = { type, number, dir, mode };

        let settingsPath = this._settings.path + key + '/';
        this._editedActionSettings = Gio.Settings.new_with_path('org.gnome.desktop.peripherals.tablet.pad-button',
                                                                settingsPath);
        this._actionEditor.setSettings(this._editedActionSettings, type);
        this._padDiagram.startEdition(type, number, dir);
    }

    _startButtonActionEdition(button) {
        let ch = String.fromCharCode('A'.charCodeAt() + button);
        let key = 'button' + ch;
        this._startActionEdition(key, Meta.PadActionType.BUTTON, button);
    }

    _startRingActionEdition(ring, dir, mode) {
        let ch = String.fromCharCode('A'.charCodeAt() + ring);
        let key = 'ring%s-%s-mode-%d'.format(ch, dir == CCW ? 'ccw' : 'cw', mode);
        this._startActionEdition(key, Meta.PadActionType.RING, ring, dir, mode);
    }

    _startStripActionEdition(strip, dir, mode) {
        let ch = String.fromCharCode('A'.charCodeAt() + strip);
        let key = 'strip%s-%s-mode-%d'.format(ch, dir == UP ? 'up' : 'down', mode);
        this._startActionEdition(key, Meta.PadActionType.STRIP, strip, dir, mode);
    }

    setEditionMode(editionMode) {
        if (this._editionMode == editionMode)
            return;

        this._editionMode = editionMode;
        this._syncEditionMode();
    }

    destroy() {
        this.actor.destroy();
    }

    _onDestroy() {
        Main.popModal(this.actor);
        this._actionEditor.close();

        let deviceManager = Clutter.DeviceManager.get_default();
        if (this._deviceRemovedId != 0) {
            deviceManager.disconnect(this._deviceRemovedId);
            this._deviceRemovedId = 0;
        }
        if (this._deviceAddedId != 0) {
            deviceManager.disconnect(this._deviceAddedId);
            this._deviceAddedId = 0;
        }

        if (this._capturedEventId != 0) {
            global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }

        this.actor = null;
        this.emit('closed');
    }
};
Signals.addSignalMethods(PadOsd.prototype);

const PadOsdIface = loadInterfaceXML('org.gnome.Shell.Wacom.PadOsd');

var PadOsdService = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(PadOsdIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Wacom');
        Gio.DBus.session.own_name('org.gnome.Shell.Wacom.PadOsd', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    ShowAsync(params, invocation) {
        let [deviceNode, editionMode] = params;
        let deviceManager = Clutter.DeviceManager.get_default();
        let devices = deviceManager.list_devices();
        let padDevice = null;

        devices.forEach(device => {
            if (deviceNode == device.get_device_node() &&
                device.get_device_type() == Clutter.InputDeviceType.PAD_DEVICE)
                padDevice = device;
        });

        if (padDevice == null) {
            invocation.return_error_literal(Gio.IOErrorEnum,
                                            Gio.IOErrorEnum.CANCELLED,
                                            "Invalid params");
            return;
        }

        global.display.request_pad_osd(padDevice, editionMode);
        invocation.return_value(null);
    }
};
Signals.addSignalMethods(PadOsdService.prototype);
(uuay)magnifier.js�	// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atspi, Clutter, GDesktopEnums,
        Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const Background = imports.ui.background;
const FocusCaretTracker = imports.ui.focusCaretTracker;
const Main = imports.ui.main;
const MagnifierDBus = imports.ui.magnifierDBus;
const Params = imports.misc.params;
const PointerWatcher = imports.ui.pointerWatcher;

var CROSSHAIRS_CLIP_SIZE = [100, 100];
var NO_CHANGE = 0.0;

var POINTER_REST_TIME = 1000; // milliseconds

// Settings
const MAGNIFIER_SCHEMA          = 'org.gnome.desktop.a11y.magnifier';
const SCREEN_POSITION_KEY       = 'screen-position';
const MAG_FACTOR_KEY            = 'mag-factor';
const INVERT_LIGHTNESS_KEY      = 'invert-lightness';
const COLOR_SATURATION_KEY      = 'color-saturation';
const BRIGHT_RED_KEY            = 'brightness-red';
const BRIGHT_GREEN_KEY          = 'brightness-green';
const BRIGHT_BLUE_KEY           = 'brightness-blue';
const CONTRAST_RED_KEY          = 'contrast-red';
const CONTRAST_GREEN_KEY        = 'contrast-green';
const CONTRAST_BLUE_KEY         = 'contrast-blue';
const LENS_MODE_KEY             = 'lens-mode';
const CLAMP_MODE_KEY            = 'scroll-at-edges';
const MOUSE_TRACKING_KEY        = 'mouse-tracking';
const FOCUS_TRACKING_KEY        = 'focus-tracking';
const CARET_TRACKING_KEY        = 'caret-tracking';
const SHOW_CROSS_HAIRS_KEY      = 'show-cross-hairs';
const CROSS_HAIRS_THICKNESS_KEY = 'cross-hairs-thickness';
const CROSS_HAIRS_COLOR_KEY     = 'cross-hairs-color';
const CROSS_HAIRS_OPACITY_KEY   = 'cross-hairs-opacity';
const CROSS_HAIRS_LENGTH_KEY    = 'cross-hairs-length';
const CROSS_HAIRS_CLIP_KEY      = 'cross-hairs-clip';

let magDBusService = null;

var MouseSpriteContent = GObject.registerClass({
    Implements: [ Clutter.Content ],
}, class MouseSpriteContent extends GObject.Object {
    _init() {
        super._init();
        this._texture = null;
    }

    vfunc_get_preferred_size() {
        if (!this._texture)
            return [false, 0, 0];

        return [true, this._texture.get_width(), this._texture.get_height()];
    }

    vfunc_paint_content(actor, node) {
        if (!this._texture)
            return;

        let color = Clutter.Color.get_static(Clutter.StaticColor.WHITE);
        let [minFilter, magFilter] = actor.get_content_scaling_filters();
        let textureNode = new Clutter.TextureNode(this._texture,
                                                  color, minFilter, magFilter);
        textureNode.set_name('MouseSpriteContent');
        node.add_child(textureNode);

        textureNode.add_rectangle(actor.get_content_box());
    }

    get texture() {
        return this._texture;
    }

    set texture(coglTexture) {
        if (this._texture == coglTexture)
            return;

        let oldTexture = this._texture;
        this._texture = coglTexture;
        this.invalidate();

        if (!oldTexture || !coglTexture ||
            oldTexture.get_width() != coglTexture.get_width() ||
            oldTexture.get_height() != coglTexture.get_height())
            this.invalidate_size();
    }
});

var Magnifier = class Magnifier {
    constructor() {
        // Magnifier is a manager of ZoomRegions.
        this._zoomRegions = [];

        // Create small clutter tree for the magnified mouse.
        let cursorTracker = Meta.CursorTracker.get_for_display(global.display);
        this._cursorTracker = cursorTracker;

        this._mouseSprite = new Clutter.Actor({ request_mode: Clutter.RequestMode.CONTENT_SIZE });
        this._mouseSprite.content = new MouseSpriteContent();

        this._cursorRoot = new Clutter.Actor();
        this._cursorRoot.add_actor(this._mouseSprite);

        // Create the first ZoomRegion and initialize it according to the
        // magnification settings.

        let mask;
        [this.xMouse, this.yMouse, mask] = global.get_pointer();

        let aZoomRegion = new ZoomRegion(this, this._cursorRoot);
        this._zoomRegions.push(aZoomRegion);
        this._settingsInit(aZoomRegion);
        aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse);

        St.Settings.get().connect('notify::magnifier-active', () => {
            this.setActive(St.Settings.get().magnifier_active);
        });

        // Export to dbus.
        magDBusService = new MagnifierDBus.ShellMagnifier();
        this.setActive(St.Settings.get().magnifier_active);
    }

    /**
     * showSystemCursor:
     * Show the system mouse pointer.
     */
    showSystemCursor() {
        this._cursorTracker.set_pointer_visible(true);
    }

    /**
     * hideSystemCursor:
     * Hide the system mouse pointer.
     */
    hideSystemCursor() {
        this._cursorTracker.set_pointer_visible(false);
    }

    /**
     * setActive:
     * Show/hide all the zoom regions.
     * @activate:   Boolean to activate or de-activate the magnifier.
     */
    setActive(activate) {
        let isActive = this.isActive();

        this._zoomRegions.forEach ((zoomRegion, index, array) => {
            zoomRegion.setActive(activate);
        });

        if (isActive != activate) {
            if (activate) {
                this._updateMouseSprite();
                this._cursorSpriteChangedId =
                    this._cursorTracker.connect('cursor-changed',
                                                this._updateMouseSprite.bind(this));
                Meta.disable_unredirect_for_display(global.display);
                this.startTrackingMouse();
            } else {
                this._cursorTracker.disconnect(this._cursorSpriteChangedId);
                this._mouseSprite.content.texture = null;
                Meta.enable_unredirect_for_display(global.display);
                this.stopTrackingMouse();
            }
        }

        // Make sure system mouse pointer is shown when all zoom regions are
        // invisible.
        if (!activate)
            this._cursorTracker.set_pointer_visible(true);

        // Notify interested parties of this change
        this.emit('active-changed', activate);
    }

    /**
     * isActive:
     * @return  Whether the magnifier is active (boolean).
     */
    isActive() {
        // Sufficient to check one ZoomRegion since Magnifier's active
        // state applies to all of them.
        if (this._zoomRegions.length == 0)
            return false;
        else
            return this._zoomRegions[0].isActive();
    }

    /**
     * startTrackingMouse:
     * Turn on mouse tracking, if not already doing so.
     */
    startTrackingMouse() {
        if (!this._pointerWatch) {
            let interval = 1000 / Clutter.get_default_frame_rate();
            this._pointerWatch = PointerWatcher.getPointerWatcher().addWatch(interval, this.scrollToMousePos.bind(this));
        }
    }

    /**
     * stopTrackingMouse:
     * Turn off mouse tracking, if not already doing so.
     */
    stopTrackingMouse() {
        if (this._pointerWatch)
            this._pointerWatch.remove();

        this._pointerWatch = null;
    }

    /**
     * isTrackingMouse:
     * Is the magnifier tracking the mouse currently?
     */
    isTrackingMouse() {
        return !!this._mouseTrackingId;
    }

    /**
     * scrollToMousePos:
     * Position all zoom regions' ROI relative to the current location of the
     * system pointer.
     * @return      true.
     */
    scrollToMousePos() {
        let [xMouse, yMouse, mask] = global.get_pointer();

        if (xMouse != this.xMouse || yMouse != this.yMouse) {
            this.xMouse = xMouse;
            this.yMouse = yMouse;

            let sysMouseOverAny = false;
            this._zoomRegions.forEach((zoomRegion, index, array) => {
                if (zoomRegion.scrollToMousePos())
                    sysMouseOverAny = true;
            });
            if (sysMouseOverAny)
                this.hideSystemCursor();
            else
                this.showSystemCursor();
        }
        return true;
    }

    /**
     * createZoomRegion:
     * Create a ZoomRegion instance with the given properties.
     * @xMagFactor:     The power to set horizontal magnification of the
     *                  ZoomRegion.  A value of 1.0 means no magnification.  A
     *                  value of 2.0 doubles the size.
     * @yMagFactor:     The power to set the vertical magnification of the
     *                  ZoomRegion.
     * @roi             Object in the form { x, y, width, height } that
     *                  defines the region to magnify.  Given in unmagnified
     *                  coordinates.
     * @viewPort        Object in the form { x, y, width, height } that defines
     *                  the position of the ZoomRegion on screen.
     * @return          The newly created ZoomRegion.
     */
    createZoomRegion(xMagFactor, yMagFactor, roi, viewPort) {
        let zoomRegion = new ZoomRegion(this, this._cursorRoot);
        zoomRegion.setViewPort(viewPort);

        // We ignore the redundant width/height on the ROI
        let fixedROI = new Object(roi);
        fixedROI.width = viewPort.width / xMagFactor;
        fixedROI.height = viewPort.height / yMagFactor;
        zoomRegion.setROI(fixedROI);

        zoomRegion.addCrosshairs(this._crossHairs);
        return zoomRegion;
    }

    /**
     * addZoomRegion:
     * Append the given ZoomRegion to the list of currently defined ZoomRegions
     * for this Magnifier instance.
     * @zoomRegion:     The zoomRegion to add.
     */
    addZoomRegion(zoomRegion) {
        if(zoomRegion) {
            this._zoomRegions.push(zoomRegion);
            if (!this.isTrackingMouse())
                this.startTrackingMouse();
        }
    }

    /**
     * getZoomRegions:
     * Return a list of ZoomRegion's for this Magnifier.
     * @return:     The Magnifier's zoom region list (array).
     */
    getZoomRegions() {
        return this._zoomRegions;
    }

    /**
     * clearAllZoomRegions:
     * Remove all the zoom regions from this Magnfier's ZoomRegion list.
     */
    clearAllZoomRegions() {
        for (let i = 0; i < this._zoomRegions.length; i++)
            this._zoomRegions[i].setActive(false);

        this._zoomRegions.length = 0;
        this.stopTrackingMouse();
        this.showSystemCursor();
    }

    /**
     * addCrosshairs:
     * Add and show a cross hair centered on the magnified mouse.
     */
    addCrosshairs() {
        if (!this._crossHairs)
            this._crossHairs = new Crosshairs();

        let thickness = this._settings.get_int(CROSS_HAIRS_THICKNESS_KEY);
        let color = this._settings.get_string(CROSS_HAIRS_COLOR_KEY);
        let opacity = this._settings.get_double(CROSS_HAIRS_OPACITY_KEY);
        let length = this._settings.get_int(CROSS_HAIRS_LENGTH_KEY);
        let clip = this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY);

        this.setCrosshairsThickness(thickness);
        this.setCrosshairsColor(color);
        this.setCrosshairsOpacity(opacity);
        this.setCrosshairsLength(length);
        this.setCrosshairsClip(clip);

        let theCrossHairs = this._crossHairs;
        this._zoomRegions.forEach ((zoomRegion, index, array) => {
            zoomRegion.addCrosshairs(theCrossHairs);
        });
    }

    /**
     * setCrosshairsVisible:
     * Show or hide the cross hair.
     * @visible    Flag that indicates show (true) or hide (false).
     */
    setCrosshairsVisible(visible) {
        if (visible) {
            if (!this._crossHairs)
                this.addCrosshairs();
            this._crossHairs.show();
        }
        else {
            if (this._crossHairs)
                this._crossHairs.hide();
        }
    }

    /**
     * setCrosshairsColor:
     * Set the color of the crosshairs for all ZoomRegions.
     * @color:  The color as a string, e.g. '#ff0000ff' or 'red'.
     */
    setCrosshairsColor(color) {
        if (this._crossHairs) {
            let [res, clutterColor] = Clutter.Color.from_string(color);
            this._crossHairs.setColor(clutterColor);
        }
    }

    /**
     * getCrosshairsColor:
     * Get the color of the crosshairs.
     * @return: The color as a string, e.g. '#0000ffff' or 'blue'.
     */
    getCrosshairsColor() {
        if (this._crossHairs) {
            let clutterColor = this._crossHairs.getColor();
            return clutterColor.to_string();
        }
        else
            return '#00000000';
    }

    /**
     * setCrosshairsThickness:
     * Set the crosshairs thickness for all ZoomRegions.
     * @thickness:  The width of the vertical and horizontal lines of the
     *              crosshairs.
     */
    setCrosshairsThickness(thickness) {
        if (this._crossHairs)
            this._crossHairs.setThickness(thickness);
    }

    /**
     * getCrosshairsThickness:
     * Get the crosshairs thickness.
     * @return: The width of the vertical and horizontal lines of the
     *          crosshairs.
     */
    getCrosshairsThickness() {
        if (this._crossHairs)
            return this._crossHairs.getThickness();
        else
            return 0;
    }

    /**
     * setCrosshairsOpacity:
     * @opacity:    Value between 0.0 (transparent) and 1.0 (fully opaque).
     */
    setCrosshairsOpacity(opacity) {
        if (this._crossHairs)
            this._crossHairs.setOpacity(opacity * 255);
    }

    /**
     * getCrosshairsOpacity:
     * @return:     Value between 0.0 (transparent) and 1.0 (fully opaque).
     */
    getCrosshairsOpacity() {
        if (this._crossHairs)
            return this._crossHairs.getOpacity() / 255.0;
        else
            return 0.0;
    }

    /**
     * setCrosshairsLength:
     * Set the crosshairs length for all ZoomRegions.
     * @length: The length of the vertical and horizontal lines making up the
     *          crosshairs.
     */
    setCrosshairsLength(length) {
        if (this._crossHairs)
            this._crossHairs.setLength(length);
    }

    /**
     * getCrosshairsLength:
     * Get the crosshairs length.
     * @return: The length of the vertical and horizontal lines making up the
     *          crosshairs.
     */
    getCrosshairsLength() {
        if (this._crossHairs)
            return this._crossHairs.getLength();
        else
            return 0;
    }

    /**
     * setCrosshairsClip:
     * Set whether the crosshairs are clipped at their intersection.
     * @clip:   Flag to indicate whether to clip the crosshairs.
     */
    setCrosshairsClip(clip) {
        if (clip) {
            if (this._crossHairs)
                this._crossHairs.setClip(CROSSHAIRS_CLIP_SIZE);
        }
        else {
            // Setting no clipping on crosshairs means a zero sized clip
            // rectangle.
            if (this._crossHairs)
                this._crossHairs.setClip([0, 0]);
        }
    }

    /**
     * getCrosshairsClip:
     * Get whether the crosshairs are clipped by the mouse image.
     * @return:   Whether the crosshairs are clipped.
     */
     getCrosshairsClip() {
        if (this._crossHairs) {
            let [clipWidth, clipHeight] = this._crossHairs.getClip();
            return (clipWidth > 0 && clipHeight > 0);
        }
        else
            return false;
     }

    //// Private methods ////

    _updateMouseSprite() {
        this._updateSpriteTexture();
        let [xHot, yHot] = this._cursorTracker.get_hot();
        this._mouseSprite.set_anchor_point(xHot, yHot);
    }

    _updateSpriteTexture() {
        let sprite = this._cursorTracker.get_sprite();

        if (sprite) {
            this._mouseSprite.content.texture = sprite;
            this._mouseSprite.show();
        } else {
            this._mouseSprite.hide();
        }
    }

    _settingsInit(zoomRegion) {
        this._settings = new Gio.Settings({ schema_id: MAGNIFIER_SCHEMA });

        this._settings.connect('changed::' + SCREEN_POSITION_KEY,
                               this._updateScreenPosition.bind(this));
        this._settings.connect('changed::' + MAG_FACTOR_KEY,
                               this._updateMagFactor.bind(this));
        this._settings.connect('changed::' + LENS_MODE_KEY,
                               this._updateLensMode.bind(this));
        this._settings.connect('changed::' + CLAMP_MODE_KEY,
                               this._updateClampMode.bind(this));
        this._settings.connect('changed::' + MOUSE_TRACKING_KEY,
                               this._updateMouseTrackingMode.bind(this));
        this._settings.connect('changed::' + FOCUS_TRACKING_KEY,
                               this._updateFocusTrackingMode.bind(this));
        this._settings.connect('changed::' + CARET_TRACKING_KEY,
                               this._updateCaretTrackingMode.bind(this));

        this._settings.connect('changed::' + INVERT_LIGHTNESS_KEY,
                               this._updateInvertLightness.bind(this));
        this._settings.connect('changed::' + COLOR_SATURATION_KEY,
                               this._updateColorSaturation.bind(this));

        this._settings.connect('changed::' + BRIGHT_RED_KEY,
                               this._updateBrightness.bind(this));
        this._settings.connect('changed::' + BRIGHT_GREEN_KEY,
                               this._updateBrightness.bind(this));
        this._settings.connect('changed::' + BRIGHT_BLUE_KEY,
                               this._updateBrightness.bind(this));

        this._settings.connect('changed::' + CONTRAST_RED_KEY,
                               this._updateContrast.bind(this));
        this._settings.connect('changed::' + CONTRAST_GREEN_KEY,
                               this._updateContrast.bind(this));
        this._settings.connect('changed::' + CONTRAST_BLUE_KEY,
                               this._updateContrast.bind(this));

        this._settings.connect('changed::' + SHOW_CROSS_HAIRS_KEY, () => {
            this.setCrosshairsVisible(this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY));
        });

        this._settings.connect('changed::' + CROSS_HAIRS_THICKNESS_KEY, () => {
            this.setCrosshairsThickness(this._settings.get_int(CROSS_HAIRS_THICKNESS_KEY));
        });

        this._settings.connect('changed::' + CROSS_HAIRS_COLOR_KEY, () => {
            this.setCrosshairsColor(this._settings.get_string(CROSS_HAIRS_COLOR_KEY));
        });

        this._settings.connect('changed::' + CROSS_HAIRS_OPACITY_KEY, () => {
            this.setCrosshairsOpacity(this._settings.get_double(CROSS_HAIRS_OPACITY_KEY));
        });

        this._settings.connect('changed::' + CROSS_HAIRS_LENGTH_KEY, () => {
            this.setCrosshairsLength(this._settings.get_int(CROSS_HAIRS_LENGTH_KEY));
        });

        this._settings.connect('changed::' + CROSS_HAIRS_CLIP_KEY, () => {
            this.setCrosshairsClip(this._settings.get_boolean(CROSS_HAIRS_CLIP_KEY));
        });

        if (zoomRegion) {
            // Mag factor is accurate to two decimal places.
            let aPref = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2));
            if (aPref != 0.0)
                zoomRegion.setMagFactor(aPref, aPref);

            aPref = this._settings.get_enum(SCREEN_POSITION_KEY);
            if (aPref)
                zoomRegion.setScreenPosition(aPref);

            zoomRegion.setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
            zoomRegion.setClampScrollingAtEdges(!this._settings.get_boolean(CLAMP_MODE_KEY));

            aPref = this._settings.get_enum(MOUSE_TRACKING_KEY);
            if (aPref)
                zoomRegion.setMouseTrackingMode(aPref);

            aPref = this._settings.get_enum(FOCUS_TRACKING_KEY);
            if (aPref)
                zoomRegion.setFocusTrackingMode(aPref);

            aPref = this._settings.get_enum(CARET_TRACKING_KEY);
            if (aPref)
                zoomRegion.setCaretTrackingMode(aPref);

            aPref = this._settings.get_boolean(INVERT_LIGHTNESS_KEY);
            if (aPref)
                zoomRegion.setInvertLightness(aPref);

            aPref = this._settings.get_double(COLOR_SATURATION_KEY);
            if (aPref)
                zoomRegion.setColorSaturation(aPref);

            let bc = {};
            bc.r = this._settings.get_double(BRIGHT_RED_KEY);
            bc.g = this._settings.get_double(BRIGHT_GREEN_KEY);
            bc.b = this._settings.get_double(BRIGHT_BLUE_KEY);
            zoomRegion.setBrightness(bc);

            bc.r = this._settings.get_double(CONTRAST_RED_KEY);
            bc.g = this._settings.get_double(CONTRAST_GREEN_KEY);
            bc.b = this._settings.get_double(CONTRAST_BLUE_KEY);
            zoomRegion.setContrast(bc);
        }

        let showCrosshairs = this._settings.get_boolean(SHOW_CROSS_HAIRS_KEY);
        this.addCrosshairs();
        this.setCrosshairsVisible(showCrosshairs);
   }

    _updateScreenPosition() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            let position = this._settings.get_enum(SCREEN_POSITION_KEY);
            this._zoomRegions[0].setScreenPosition(position);
            if (position != GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN)
                this._updateLensMode();
        }
    }

    _updateMagFactor() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            // Mag factor is accurate to two decimal places.
            let magFactor = parseFloat(this._settings.get_double(MAG_FACTOR_KEY).toFixed(2));
            this._zoomRegions[0].setMagFactor(magFactor, magFactor);
        }
    }

    _updateLensMode() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setLensMode(this._settings.get_boolean(LENS_MODE_KEY));
        }
    }

    _updateClampMode() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setClampScrollingAtEdges(
                !this._settings.get_boolean(CLAMP_MODE_KEY)
            );
        }
    }

    _updateMouseTrackingMode() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setMouseTrackingMode(
                this._settings.get_enum(MOUSE_TRACKING_KEY)
            );
        }
    }

    _updateFocusTrackingMode() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setFocusTrackingMode(
                this._settings.get_enum(FOCUS_TRACKING_KEY)
            );
        }
    }

    _updateCaretTrackingMode() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setCaretTrackingMode(
                this._settings.get_enum(CARET_TRACKING_KEY)
            );
        }
    }

    _updateInvertLightness() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setInvertLightness(
                this._settings.get_boolean(INVERT_LIGHTNESS_KEY)
            );
        }
    }

    _updateColorSaturation() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            this._zoomRegions[0].setColorSaturation(
                this._settings.get_double(COLOR_SATURATION_KEY)
            );
        }
    }

    _updateBrightness() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            let brightness = {};
            brightness.r = this._settings.get_double(BRIGHT_RED_KEY);
            brightness.g = this._settings.get_double(BRIGHT_GREEN_KEY);
            brightness.b = this._settings.get_double(BRIGHT_BLUE_KEY);
            this._zoomRegions[0].setBrightness(brightness);
        }
    }

    _updateContrast() {
        // Applies only to the first zoom region.
        if (this._zoomRegions.length) {
            let contrast = {};
            contrast.r = this._settings.get_double(CONTRAST_RED_KEY);
            contrast.g = this._settings.get_double(CONTRAST_GREEN_KEY);
            contrast.b = this._settings.get_double(CONTRAST_BLUE_KEY);
            this._zoomRegions[0].setContrast(contrast);
        }
    }
};
Signals.addSignalMethods(Magnifier.prototype);

var ZoomRegion = class ZoomRegion {
    constructor(magnifier, mouseSourceActor) {
        this._magnifier = magnifier;
        this._focusCaretTracker = new FocusCaretTracker.FocusCaretTracker();

        this._mouseTrackingMode = GDesktopEnums.MagnifierMouseTrackingMode.NONE;
        this._focusTrackingMode = GDesktopEnums.MagnifierFocusTrackingMode.NONE;
        this._caretTrackingMode = GDesktopEnums.MagnifierCaretTrackingMode.NONE;
        this._clampScrollingAtEdges = false;
        this._lensMode = false;
        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
        this._invertLightness = false;
        this._colorSaturation = 1.0;
        this._brightness = { r: NO_CHANGE, g: NO_CHANGE, b: NO_CHANGE };
        this._contrast = { r: NO_CHANGE, g: NO_CHANGE, b: NO_CHANGE };

        this._magView = null;
        this._background = null;
        this._uiGroupClone = null;
        this._mouseSourceActor = mouseSourceActor;
        this._mouseActor  = null;
        this._crossHairs = null;
        this._crossHairsActor = null;

        this._viewPortX = 0;
        this._viewPortY = 0;
        this._viewPortWidth = global.screen_width;
        this._viewPortHeight = global.screen_height;
        this._xCenter = this._viewPortWidth / 2;
        this._yCenter = this._viewPortHeight / 2;
        this._xMagFactor = 1;
        this._yMagFactor = 1;
        this._followingCursor = false;
        this._xFocus = 0;
        this._yFocus = 0;
        this._xCaret = 0;
        this._yCaret = 0;

        this._pointerIdleMonitor = Meta.IdleMonitor.get_for_device(Meta.VIRTUAL_CORE_POINTER_ID);
        this._scrollContentsTimerId = 0;
    }

    _connectSignals() {
        if (this._signalConnections)
            return;

        this._signalConnections = [];
        let id = Main.layoutManager.connect('monitors-changed',
                                            this._monitorsChanged.bind(this));
        this._signalConnections.push([Main.layoutManager, id]);

        id = this._focusCaretTracker.connect('caret-moved', this._updateCaret.bind(this));
        this._signalConnections.push([this._focusCaretTracker, id]);

        id = this._focusCaretTracker.connect('focus-changed', this._updateFocus.bind(this));
        this._signalConnections.push([this._focusCaretTracker, id]);
    }

    _disconnectSignals() {
        for (let [obj, id] of this._signalConnections)
            obj.disconnect(id);

        delete this._signalConnections;
    }

    _updateScreenPosition() {
        if (this._screenPosition == GDesktopEnums.MagnifierScreenPosition.NONE)
            this._setViewPort({
                x: this._viewPortX,
                y: this._viewPortY,
                width: this._viewPortWidth,
                height: this._viewPortHeight
            });
        else
            this.setScreenPosition(this._screenPosition);
    }

    _updateFocus(caller, event) {
        let component = event.source.get_component_iface();
        if (!component || event.detail1 != 1)
            return;
        let extents;
        try {
            extents = component.get_extents(Atspi.CoordType.SCREEN);
        } catch(e) {
            log('Failed to read extents of focused component: ' + e.message);
            return;
        }

        [this._xFocus, this._yFocus] = [extents.x + (extents.width / 2),
                                        extents.y + (extents.height / 2)];
        this._centerFromFocusPosition();
    }

    _updateCaret(caller, event) {
        let text = event.source.get_text_iface();
        if (!text)
            return;
        let extents;
        try {
            extents = text.get_character_extents(text.get_caret_offset(), 0);
        } catch(e) {
            log('Failed to read extents of text caret: ' + e.message);
            return;
        }

        [this._xCaret, this._yCaret] = [extents.x, extents.y];
        this._centerFromCaretPosition();
    }

    /**
     * setActive:
     * @activate:   Boolean to show/hide the ZoomRegion.
     */
    setActive(activate) {
        if (activate == this.isActive())
            return;

        if (activate) {
            this._createActors();
            if (this._isMouseOverRegion())
                this._magnifier.hideSystemCursor();
            this._updateScreenPosition();
            this._updateMagViewGeometry();
            this._updateCloneGeometry();
            this._updateMousePosition();
            this._connectSignals();
        } else {
            this._disconnectSignals();
            this._destroyActors();
        }

        this._syncCaretTracking();
        this._syncFocusTracking();
    }

    /**
     * isActive:
     * @return  Whether this ZoomRegion is active (boolean).
     */
    isActive() {
        return this._magView != null;
    }

    /**
     * setMagFactor:
     * @xMagFactor:     The power to set the horizontal magnification factor to
     *                  of the magnified view.  A value of 1.0 means no
     *                  magnification.  A value of 2.0 doubles the size.
     * @yMagFactor:     The power to set the vertical magnification factor to
     *                  of the magnified view.
     */
    setMagFactor(xMagFactor, yMagFactor) {
        this._changeROI({ xMagFactor: xMagFactor,
                          yMagFactor: yMagFactor,
                          redoCursorTracking: this._followingCursor });
    }

    /**
     * getMagFactor:
     * @return  an array, [xMagFactor, yMagFactor], containing the horizontal
     *          and vertical magnification powers.  A value of 1.0 means no
     *          magnification.  A value of 2.0 means the contents are doubled
     *          in size, and so on.
     */
    getMagFactor() {
        return [this._xMagFactor, this._yMagFactor];
    }

    /**
     * setMouseTrackingMode
     * @mode:     One of the enum MouseTrackingMode values.
     */
    setMouseTrackingMode(mode) {
        if (mode >= GDesktopEnums.MagnifierMouseTrackingMode.NONE &&
            mode <= GDesktopEnums.MagnifierMouseTrackingMode.PUSH)
            this._mouseTrackingMode = mode;
    }

    /**
     * getMouseTrackingMode
     * @return:     One of the enum MouseTrackingMode values.
     */
    getMouseTrackingMode() {
        return this._mouseTrackingMode;
    }

    /**
     * setFocusTrackingMode
     * @mode:     One of the enum FocusTrackingMode values.
     */
    setFocusTrackingMode(mode) {
        this._focusTrackingMode = mode;
        this._syncFocusTracking();
    }

    /**
     * setCaretTrackingMode
     * @mode:     One of the enum CaretTrackingMode values.
     */
    setCaretTrackingMode(mode) {
        this._caretTrackingMode = mode;
        this._syncCaretTracking();
    }

    _syncFocusTracking() {
        let enabled = this._focusTrackingMode != GDesktopEnums.MagnifierFocusTrackingMode.NONE &&
            this.isActive();

        if (enabled)
            this._focusCaretTracker.registerFocusListener();
        else
            this._focusCaretTracker.deregisterFocusListener();
    }

    _syncCaretTracking() {
        let enabled = this._caretTrackingMode != GDesktopEnums.MagnifierCaretTrackingMode.NONE &&
            this.isActive();

        if (enabled)
            this._focusCaretTracker.registerCaretListener();
        else
            this._focusCaretTracker.deregisterCaretListener();
    }

    /**
     * setViewPort
     * Sets the position and size of the ZoomRegion on screen.
     * @viewPort:   Object defining the position and size of the view port.
     *              It has members x, y, width, height.  The values are in
     *              stage coordinate space.
     */
    setViewPort(viewPort) {
        this._setViewPort(viewPort);
        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.NONE;
    }

    /**
     * setROI
     * Sets the "region of interest" that the ZoomRegion is magnifying.
     * @roi:    Object that defines the region of the screen to magnify.  It
     *          has members x, y, width, height.  The values are in
     *          screen (unmagnified) coordinate space.
     */
    setROI(roi) {
        if (roi.width <= 0 || roi.height <= 0)
            return;

        this._followingCursor = false;
        this._changeROI({ xMagFactor: this._viewPortWidth / roi.width,
                          yMagFactor: this._viewPortHeight / roi.height,
                          xCenter: roi.x + roi.width  / 2,
                          yCenter: roi.y + roi.height / 2 });
    }

    /**
     * getROI:
     * Retrieves the "region of interest" -- the rectangular bounds of that part
     * of the desktop that the magnified view is showing (x, y, width, height).
     * The bounds are given in non-magnified coordinates.
     * @return  an array, [x, y, width, height], representing the bounding
     *          rectangle of what is shown in the magnified view.
     */
    getROI() {
        let roiWidth = this._viewPortWidth / this._xMagFactor;
        let roiHeight = this._viewPortHeight / this._yMagFactor;

        return [this._xCenter - roiWidth / 2,
                this._yCenter - roiHeight / 2,
                roiWidth, roiHeight];
    }

    /**
     * setLensMode:
     * Turn lens mode on/off.  In full screen mode, lens mode does nothing since
     * a lens the size of the screen is pointless.
     * @lensMode:   A boolean to set the sense of lens mode.
     */
    setLensMode(lensMode) {
        this._lensMode = lensMode;
        if (!this._lensMode)
            this.setScreenPosition (this._screenPosition);
    }

    /**
     * isLensMode:
     * Is lens mode on or off?
     * @return  The lens mode state as a boolean.
     */
    isLensMode() {
        return this._lensMode;
    }

    /**
     * setClampScrollingAtEdges:
     * Stop vs. allow scrolling of the magnified contents when it scroll beyond
     * the edges of the screen.
     * @clamp:   Boolean to turn on/off clamping.
     */
    setClampScrollingAtEdges(clamp) {
        this._clampScrollingAtEdges = clamp;
        if (clamp)
            this._changeROI();
    }

    /**
     * setTopHalf:
     * Magnifier view occupies the top half of the screen.
     */
    setTopHalf() {
        let viewPort = {};
        viewPort.x = 0;
        viewPort.y = 0;
        viewPort.width = global.screen_width;
        viewPort.height = global.screen_height/2;
        this._setViewPort(viewPort);
        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.TOP_HALF;
    }

    /**
     * setBottomHalf:
     * Magnifier view occupies the bottom half of the screen.
     */
    setBottomHalf() {
        let viewPort = {};
        viewPort.x = 0;
        viewPort.y = global.screen_height/2;
        viewPort.width = global.screen_width;
        viewPort.height = global.screen_height/2;
        this._setViewPort(viewPort);
        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.BOTTOM_HALF;
    }

    /**
     * setLeftHalf:
     * Magnifier view occupies the left half of the screen.
     */
    setLeftHalf() {
        let viewPort = {};
        viewPort.x = 0;
        viewPort.y = 0;
        viewPort.width = global.screen_width/2;
        viewPort.height = global.screen_height;
        this._setViewPort(viewPort);
        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.LEFT_HALF;
    }

    /**
     * setRightHalf:
     * Magnifier view occupies the right half of the screen.
     */
    setRightHalf() {
        let viewPort = {};
        viewPort.x = global.screen_width/2;
        viewPort.y = 0;
        viewPort.width = global.screen_width/2;
        viewPort.height = global.screen_height;
        this._setViewPort(viewPort);
        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.RIGHT_HALF;
    }

    /**
     * setFullScreenMode:
     * Set the ZoomRegion to full-screen mode.
     * Note:  disallows lens mode.
     */
    setFullScreenMode() {
        let viewPort = {};
        viewPort.x = 0;
        viewPort.y = 0;
        viewPort.width = global.screen_width;
        viewPort.height = global.screen_height;
        this.setViewPort(viewPort);

        this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;
    }

    /**
     * setScreenPosition:
     * Positions the zoom region to one of the enumerated positions on the
     * screen.
     * @position:   one of Magnifier.FULL_SCREEN, Magnifier.TOP_HALF,
     *              Magnifier.BOTTOM_HALF,Magnifier.LEFT_HALF, or
     *              Magnifier.RIGHT_HALF.
     */
    setScreenPosition(inPosition) {
        switch (inPosition) {
            case GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN:
                this.setFullScreenMode();
                break;
            case GDesktopEnums.MagnifierScreenPosition.TOP_HALF:
                this.setTopHalf();
                break;
            case GDesktopEnums.MagnifierScreenPosition.BOTTOM_HALF:
                this.setBottomHalf();
                break;
            case GDesktopEnums.MagnifierScreenPosition.LEFT_HALF:
                this.setLeftHalf();
                break;
            case GDesktopEnums.MagnifierScreenPosition.RIGHT_HALF:
                this.setRightHalf();
                break;
        }
    }

    /**
     * getScreenPosition:
     * Tell the outside world what the current mode is -- magnifiying the
     * top half, bottom half, etc.
     * @return:  the current mode.
     */
    getScreenPosition() {
        return this._screenPosition;
    }

    /**
     * scrollToMousePos:
     * Set the region of interest based on the position of the system pointer.
     * @return:     Whether the system mouse pointer is over the magnified view.
     */
    scrollToMousePos() {
        this._followingCursor = true;
        if (this._mouseTrackingMode != GDesktopEnums.MagnifierMouseTrackingMode.NONE)
            this._changeROI({ redoCursorTracking: true });
        else
            this._updateMousePosition();

        // Determine whether the system mouse pointer is over this zoom region.
        return this._isMouseOverRegion();
    }

    _clearScrollContentsTimer() {
        if (this._scrollContentsTimerId != 0) {
            Mainloop.source_remove(this._scrollContentsTimerId);
            this._scrollContentsTimerId = 0;
        }
    }

    _scrollContentsToDelayed(x, y) {
        if (this._pointerIdleMonitor.get_idletime() >= POINTER_REST_TIME) {
            this.scrollContentsTo(x, y);
            return;
        }

        this._clearScrollContentsTimer();
        this._scrollContentsTimerId = Mainloop.timeout_add(POINTER_REST_TIME, () => {
            this._scrollContentsToDelayed(x, y);
            return GLib.SOURCE_REMOVE;
        });
    }

    /**
     * scrollContentsTo:
     * Shift the contents of the magnified view such it is centered on the given
     * coordinate.
     * @x:      The x-coord of the point to center on.
     * @y:      The y-coord of the point to center on.
     */
    scrollContentsTo(x, y) {
        this._clearScrollContentsTimer();

        this._followingCursor = false;
        this._changeROI({ xCenter: x,
                          yCenter: y });
    }

    /**
     * addCrosshairs:
     * Add crosshairs centered on the magnified mouse.
     * @crossHairs: Crosshairs instance
     */
    addCrosshairs(crossHairs) {
        this._crossHairs = crossHairs;

        // If the crossHairs is not already within a larger container, add it
        // to this zoom region.  Otherwise, add a clone.
        if (crossHairs && this.isActive()) {
            this._crossHairsActor = crossHairs.addToZoomRegion(this, this._mouseActor);
        }
    }

    /**
     * setInvertLightness:
     * Set whether to invert the lightness of the magnified view.
     * @flag    Boolean to either invert brightness (true), or not (false).
     */
    setInvertLightness(flag) {
        this._invertLightness = flag;
        if (this._magShaderEffects)
            this._magShaderEffects.setInvertLightness(this._invertLightness);
    }

    /**
     * getInvertLightness:
     * Retrieve whether the lightness is inverted.
     * @return    Boolean indicating inversion (true), or not (false).
     */
    getInvertLightness() {
        return this._invertLightness;
    }

    /**
     * setColorSaturation:
     * Set the color saturation of the magnified view.
     * @sauration  A value from 0.0 to 1.0 that defines the color
     *             saturation, with 0.0 defining no color (grayscale),
     *             and 1.0 defining full color.
     */
    setColorSaturation(saturation) {
        this._colorSaturation = saturation;
        if (this._magShaderEffects)
            this._magShaderEffects.setColorSaturation(this._colorSaturation);
    }

    /**
     * getColorSaturation:
     * Retrieve the color saturation of the magnified view.
     */
    getColorSaturation() {
        return this._colorSaturation;
    }

    /**
     * setBrightness:
     * Alter the brightness of the magnified view.
     * @brightness  Object containing the contrast for the red, green,
     *              and blue channels.  Values of 0.0 represent "standard"
     *              brightness (no change), whereas values less or greater than
     *              0.0 indicate decreased or incresaed brightness, respectively.
     */
    setBrightness(brightness) {
        this._brightness.r = brightness.r;
        this._brightness.g = brightness.g;
        this._brightness.b = brightness.b;
        if (this._magShaderEffects)
            this._magShaderEffects.setBrightness(this._brightness);
    }

    /**
     * setContrast:
     * Alter the contrast of the magnified view.
     * @contrast    Object containing the contrast for the red, green,
     *              and blue channels.  Values of 0.0 represent "standard"
     *              contrast (no change), whereas values less or greater than
     *              0.0 indicate decreased or incresaed contrast, respectively.
     */
    setContrast(contrast) {
        this._contrast.r = contrast.r;
        this._contrast.g = contrast.g;
        this._contrast.b = contrast.b;
        if (this._magShaderEffects)
            this._magShaderEffects.setContrast(this._contrast);
    }

    /**
     * getContrast:
     * Retreive the contrast of the magnified view.
     * @return  Object containing the contrast for the red, green,
     *          and blue channels.
     */
    getContrast() {
        let contrast = {};
        contrast.r = this._contrast.r;
        contrast.g = this._contrast.g;
        contrast.b = this._contrast.b;
        return contrast;
    }

    //// Private methods ////

    _createActors() {
        // The root actor for the zoom region
        this._magView = new St.Bin({ style_class: 'magnifier-zoom-region', x_fill: true, y_fill: true });
        global.stage.add_actor(this._magView);

        // hide the magnified region from CLUTTER_PICK_ALL
        Shell.util_set_hidden_from_pick (this._magView, true);

        // Add a group to clip the contents of the magnified view.
        let mainGroup = new Clutter.Actor({ clip_to_allocation: true });
        this._magView.set_child(mainGroup);

        // Add a background for when the magnified uiGroup is scrolled
        // out of view (don't want to see desktop showing through).
        this._background = (new Background.SystemBackground()).actor;
        mainGroup.add_actor(this._background);

        // Clone the group that contains all of UI on the screen.  This is the
        // chrome, the windows, etc.
        this._uiGroupClone = new Clutter.Clone({ source: Main.uiGroup,
                                                 clip_to_allocation: true });
        mainGroup.add_actor(this._uiGroupClone);

        // Add either the given mouseSourceActor to the ZoomRegion, or a clone of
        // it.
        if (this._mouseSourceActor.get_parent() != null)
            this._mouseActor = new Clutter.Clone({ source: this._mouseSourceActor });
        else
            this._mouseActor = this._mouseSourceActor;
        mainGroup.add_actor(this._mouseActor);

        if (this._crossHairs)
            this._crossHairsActor = this._crossHairs.addToZoomRegion(this, this._mouseActor);
        else
            this._crossHairsActor = null;

        // Contrast and brightness effects.
        this._magShaderEffects = new MagShaderEffects(this._uiGroupClone);
        this._magShaderEffects.setColorSaturation(this._colorSaturation);
        this._magShaderEffects.setInvertLightness(this._invertLightness);
        this._magShaderEffects.setBrightness(this._brightness);
        this._magShaderEffects.setContrast(this._contrast);
    }

    _destroyActors() {
        if (this._mouseActor == this._mouseSourceActor)
            this._mouseActor.get_parent().remove_actor (this._mouseActor);
        if (this._crossHairs)
            this._crossHairs.removeFromParent(this._crossHairsActor);

        this._magShaderEffects.destroyEffects();
        this._magShaderEffects = null;
        this._magView.destroy();
        this._magView = null;
        this._background = null;
        this._uiGroupClone = null;
        this._mouseActor = null;
        this._crossHairsActor = null;
    }

    _setViewPort(viewPort, fromROIUpdate) {
        // Sets the position of the zoom region on the screen

        let width = Math.round(Math.min(viewPort.width, global.screen_width));
        let height = Math.round(Math.min(viewPort.height, global.screen_height));
        let x = Math.max(viewPort.x, 0);
        let y = Math.max(viewPort.y, 0);

        x = Math.round(Math.min(x, global.screen_width - width));
        y = Math.round(Math.min(y, global.screen_height - height));

        this._viewPortX = x;
        this._viewPortY = y;
        this._viewPortWidth = width;
        this._viewPortHeight = height;

        this._updateMagViewGeometry();

        if (!fromROIUpdate)
            this._changeROI({ redoCursorTracking: this._followingCursor }); // will update mouse

        if (this.isActive() && this._isMouseOverRegion())
            this._magnifier.hideSystemCursor();
    }

    _changeROI(params) {
        // Updates the area we are viewing; the magnification factors
        // and center can be set explicitly, or we can recompute
        // the position based on the mouse cursor position

        params = Params.parse(params, { xMagFactor: this._xMagFactor,
                                        yMagFactor: this._yMagFactor,
                                        xCenter: this._xCenter,
                                        yCenter: this._yCenter,
                                        redoCursorTracking: false });

        if (params.xMagFactor <= 0)
            params.xMagFactor = this._xMagFactor;
        if (params.yMagFactor <= 0)
            params.yMagFactor = this._yMagFactor;

        this._xMagFactor = params.xMagFactor;
        this._yMagFactor = params.yMagFactor;

        if (params.redoCursorTracking &&
            this._mouseTrackingMode != GDesktopEnums.MagnifierMouseTrackingMode.NONE) {
            // This depends on this.xMagFactor/yMagFactor already being updated
            [params.xCenter, params.yCenter] = this._centerFromMousePosition();
        }

        if (this._clampScrollingAtEdges) {
            let roiWidth = this._viewPortWidth / this._xMagFactor;
            let roiHeight = this._viewPortHeight / this._yMagFactor;

            params.xCenter = Math.min(params.xCenter, global.screen_width - roiWidth / 2);
            params.xCenter = Math.max(params.xCenter, roiWidth / 2);
            params.yCenter = Math.min(params.yCenter, global.screen_height - roiHeight / 2);
            params.yCenter = Math.max(params.yCenter, roiHeight / 2);
        }

        this._xCenter = params.xCenter;
        this._yCenter = params.yCenter;

        // If in lens mode, move the magnified view such that it is centered
        // over the actual mouse. However, in full screen mode, the "lens" is
        // the size of the screen -- pointless to move such a large lens around.
        if (this._lensMode && !this._isFullScreen())
            this._setViewPort({ x: this._xCenter - this._viewPortWidth / 2,
                                y: this._yCenter - this._viewPortHeight / 2,
                                width: this._viewPortWidth,
                                height: this._viewPortHeight }, true);

        this._updateCloneGeometry();
        this._updateMousePosition();
    }

    _isMouseOverRegion() {
        // Return whether the system mouse sprite is over this ZoomRegion.  If the
        // mouse's position is not given, then it is fetched.
        let mouseIsOver = false;
        if (this.isActive()) {
            let xMouse = this._magnifier.xMouse;
            let yMouse = this._magnifier.yMouse;

            mouseIsOver = (
                xMouse >= this._viewPortX && xMouse < (this._viewPortX + this._viewPortWidth) &&
                yMouse >= this._viewPortY && yMouse < (this._viewPortY + this._viewPortHeight)
            );
        }
        return mouseIsOver;
    }

    _isFullScreen() {
        // Does the magnified view occupy the whole screen? Note that this
        // doesn't necessarily imply
        // this._screenPosition = GDesktopEnums.MagnifierScreenPosition.FULL_SCREEN;

        if (this._viewPortX != 0 || this._viewPortY != 0)
            return false;
        if (this._viewPortWidth != global.screen_width ||
            this._viewPortHeight != global.screen_height)
            return false;
        return true;
    }

    _centerFromMousePosition() {
        // Determines where the center should be given the current cursor
        // position and mouse tracking mode

        let xMouse = this._magnifier.xMouse;
        let yMouse = this._magnifier.yMouse;

        if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PROPORTIONAL) {
            return this._centerFromPointProportional(xMouse, yMouse);
        }
        else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.PUSH) {
            return this._centerFromPointPush(xMouse, yMouse);
        }
        else if (this._mouseTrackingMode == GDesktopEnums.MagnifierMouseTrackingMode.CENTERED) {
            return this._centerFromPointCentered(xMouse, yMouse);
        }

        return null; // Should never be hit
    }

    _centerFromCaretPosition() {
        let xCaret = this._xCaret;
        let yCaret = this._yCaret;

        if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PROPORTIONAL)
            [xCaret, yCaret] = this._centerFromPointProportional(xCaret, yCaret);
        else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.PUSH)
            [xCaret, yCaret] = this._centerFromPointPush(xCaret, yCaret);
        else if (this._caretTrackingMode == GDesktopEnums.MagnifierCaretTrackingMode.CENTERED)
            [xCaret, yCaret] = this._centerFromPointCentered(xCaret, yCaret);

        this._scrollContentsToDelayed(xCaret, yCaret);
    }

    _centerFromFocusPosition() {
        let xFocus = this._xFocus;
        let yFocus = this._yFocus;

        if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PROPORTIONAL)
            [xFocus, yFocus] = this._centerFromPointProportional(xFocus, yFocus);
        else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.PUSH)
            [xFocus, yFocus] = this._centerFromPointPush(xFocus, yFocus);
        else if (this._focusTrackingMode == GDesktopEnums.MagnifierFocusTrackingMode.CENTERED)
            [xFocus, yFocus] = this._centerFromPointCentered(xFocus, yFocus);

        this._scrollContentsToDelayed(xFocus, yFocus);
    }

    _centerFromPointPush(xPoint, yPoint) {
        let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
        let [cursorWidth, cursorHeight] = this._mouseSourceActor.get_size();
        let xPos = xRoi + widthRoi / 2;
        let yPos = yRoi + heightRoi / 2;
        let xRoiRight = xRoi + widthRoi - cursorWidth;
        let yRoiBottom = yRoi + heightRoi - cursorHeight;

        if (xPoint < xRoi)
            xPos -= (xRoi - xPoint);
        else if (xPoint > xRoiRight)
            xPos += (xPoint - xRoiRight);

        if (yPoint < yRoi)
            yPos -= (yRoi - yPoint);
        else if (yPoint > yRoiBottom)
            yPos += (yPoint - yRoiBottom);

        return [xPos, yPos];
    }

    _centerFromPointProportional(xPoint, yPoint) {
        let [xRoi, yRoi, widthRoi, heightRoi] = this.getROI();
        let halfScreenWidth = global.screen_width / 2;
        let halfScreenHeight = global.screen_height / 2;
        // We want to pad with a constant distance after zooming, so divide
        // by the magnification factor.
        let unscaledPadding = Math.min(this._viewPortWidth, this._viewPortHeight) / 5;
        let xPadding = unscaledPadding / this._xMagFactor;
        let yPadding = unscaledPadding / this._yMagFactor;
        let xProportion = (xPoint - halfScreenWidth) / halfScreenWidth;   // -1 ... 1
        let yProportion = (yPoint - halfScreenHeight) / halfScreenHeight; // -1 ... 1
        let xPos = xPoint - xProportion * (widthRoi / 2 - xPadding);
        let yPos = yPoint - yProportion * (heightRoi /2 - yPadding);

        return [xPos, yPos];
    }

    _centerFromPointCentered(xPoint, yPoint) {
        return [xPoint, yPoint];
    }

    _screenToViewPort(screenX, screenY) {
        // Converts coordinates relative to the (unmagnified) screen to coordinates
        // relative to the origin of this._magView
        return [this._viewPortWidth / 2 + (screenX - this._xCenter) * this._xMagFactor,
                this._viewPortHeight / 2 + (screenY - this._yCenter) * this._yMagFactor];
    }

    _updateMagViewGeometry() {
        if (!this.isActive())
            return;

        if (this._isFullScreen())
            this._magView.add_style_class_name('full-screen');
        else
            this._magView.remove_style_class_name('full-screen');

        this._magView.set_size(this._viewPortWidth, this._viewPortHeight);
        this._magView.set_position(this._viewPortX, this._viewPortY);
    }

    _updateCloneGeometry() {
        if (!this.isActive())
            return;

        this._uiGroupClone.set_scale(this._xMagFactor, this._yMagFactor);
        this._mouseActor.set_scale(this._xMagFactor, this._yMagFactor);

        let [x, y] = this._screenToViewPort(0, 0);
        this._uiGroupClone.set_position(Math.round(x), Math.round(y));

        this._updateMousePosition();
    }

    _updateMousePosition() {
        if (!this.isActive())
            return;

        let [xMagMouse, yMagMouse] = this._screenToViewPort(this._magnifier.xMouse,
                                                            this._magnifier.yMouse);

        xMagMouse = Math.round(xMagMouse);
        yMagMouse = Math.round(yMagMouse);

        this._mouseActor.set_position(xMagMouse, yMagMouse);

        if (this._crossHairsActor) {
            let [groupWidth, groupHeight] = this._crossHairsActor.get_size();
            this._crossHairsActor.set_position(xMagMouse - groupWidth / 2,
                                               yMagMouse - groupHeight / 2);
        }
    }

    _monitorsChanged() {
        this._background.set_size(global.screen_width, global.screen_height);
        this._updateScreenPosition();
    }
};

var Crosshairs = class Crosshairs {
    constructor() {

        // Set the group containing the crosshairs to three times the desktop
        // size in case the crosshairs need to appear to be infinite in
        // length (i.e., extend beyond the edges of the view they appear in).
        let groupWidth = global.screen_width * 3;
        let groupHeight = global.screen_height * 3;

        this._actor = new Clutter.Actor({
            clip_to_allocation: false,
            width: groupWidth,
            height: groupHeight
        });
        this._horizLeftHair = new Clutter.Actor();
        this._horizRightHair = new Clutter.Actor();
        this._vertTopHair = new Clutter.Actor();
        this._vertBottomHair = new Clutter.Actor();
        this._actor.add_actor(this._horizLeftHair);
        this._actor.add_actor(this._horizRightHair);
        this._actor.add_actor(this._vertTopHair);
        this._actor.add_actor(this._vertBottomHair);
        this._clipSize = [0, 0];
        this._clones = [];
        this.reCenter();

        Main.layoutManager.connect('monitors-changed',
                                   this._monitorsChanged.bind(this));
    }

    _monitorsChanged() {
        this._actor.set_size(global.screen_width * 3, global.screen_height * 3);
        this.reCenter();
    }

   /**
    * addToZoomRegion
    * Either add the crosshairs actor to the given ZoomRegion, or, if it is
    * already part of some other ZoomRegion, create a clone of the crosshairs
    * actor, and add the clone instead.  Returns either the original or the
    * clone.
    * @zoomRegion:      The container to add the crosshairs group to.
    * @magnifiedMouse:  The mouse actor for the zoom region -- used to
    *                   position the crosshairs and properly layer them below
    *                   the mouse.
    * @return           The crosshairs actor, or its clone.
    */
    addToZoomRegion(zoomRegion, magnifiedMouse) {
        let crosshairsActor = null;
        if (zoomRegion && magnifiedMouse) {
            let container = magnifiedMouse.get_parent();
            if (container) {
                crosshairsActor = this._actor;
                if (this._actor.get_parent() != null) {
                    crosshairsActor = new Clutter.Clone({ source: this._actor });
                    this._clones.push(crosshairsActor);
                }
                crosshairsActor.visible = this._actor.visible;

                container.add_actor(crosshairsActor);
                container.raise_child(magnifiedMouse, crosshairsActor);
                let [xMouse, yMouse] = magnifiedMouse.get_position();
                let [crosshairsWidth, crosshairsHeight] = crosshairsActor.get_size();
                crosshairsActor.set_position(xMouse - crosshairsWidth / 2 , yMouse - crosshairsHeight / 2);
            }
        }
        return crosshairsActor;
    }

    /**
     * removeFromParent:
     * @childActor: the actor returned from addToZoomRegion
     * Remove the crosshairs actor from its parent container, or destroy the
     * child actor if it was just a clone of the crosshairs actor.
     */
    removeFromParent(childActor) {
        if (childActor == this._actor)
            childActor.get_parent().remove_actor(childActor);
        else
            childActor.destroy();
    }

    /**
     * setColor:
     * Set the color of the crosshairs.
     * @clutterColor:   The color as a Clutter.Color.
     */
    setColor(clutterColor) {
        this._horizLeftHair.background_color = clutterColor;
        this._horizRightHair.background_color = clutterColor;
        this._vertTopHair.background_color = clutterColor;
        this._vertBottomHair.background_color = clutterColor;
    }

    /**
     * getColor:
     * Get the color of the crosshairs.
     * @color:  The color as a Clutter.Color.
     */
    getColor() {
        return this._horizLeftHair.get_color();
    }

    /**
     * setThickness:
     * Set the width of the vertical and horizontal lines of the crosshairs.
     * @thickness
     */
    setThickness(thickness) {
        this._horizLeftHair.set_height(thickness);
        this._horizRightHair.set_height(thickness);
        this._vertTopHair.set_width(thickness);
        this._vertBottomHair.set_width(thickness);
        this.reCenter();
    }

    /**
     * getThickness:
     * Get the width of the vertical and horizontal lines of the crosshairs.
     * @return:     The thickness of the crosshairs.
     */
    getThickness() {
        return this._horizLeftHair.get_height();
    }

    /**
     * setOpacity:
     * Set how opaque the crosshairs are.
     * @opacity:    Value between 0 (fully transparent) and 255 (full opaque).
     */
    setOpacity(opacity) {
        // set_opacity() throws an exception for values outside the range
        // [0, 255].
        if (opacity < 0)
            opacity = 0;
        else if (opacity > 255)
            opacity = 255;

        this._horizLeftHair.set_opacity(opacity);
        this._horizRightHair.set_opacity(opacity);
        this._vertTopHair.set_opacity(opacity);
        this._vertBottomHair.set_opacity(opacity);
    }

    /**
     * setLength:
     * Set the length of the vertical and horizontal lines in the crosshairs.
     * @length: The length of the crosshairs.
     */
    setLength(length) {
        this._horizLeftHair.set_width(length);
        this._horizRightHair.set_width(length);
        this._vertTopHair.set_height(length);
        this._vertBottomHair.set_height(length);
        this.reCenter();
    }

    /**
     * getLength:
     * Get the length of the vertical and horizontal lines in the crosshairs.
     * @return: The length of the crosshairs.
     */
    getLength() {
        return this._horizLeftHair.get_width();
    }

    /**
     * setClip:
     * Set the width and height of the rectangle that clips the crosshairs at
     * their intersection
     * @size:   Array of [width, height] defining the size of the clip
     *          rectangle.
     */
    setClip(size) {
        if (size) {
            // Take a chunk out of the crosshairs where it intersects the
            // mouse.
            this._clipSize = size;
            this.reCenter();
        }
        else {
            // Restore the missing chunk.
            this._clipSize = [0, 0];
            this.reCenter();
        }
     }

    /**
     * show:
     * Show the crosshairs.
     */
    show() {
        this._actor.show();
        // Clones don't share visibility.
        for (let i = 0; i < this._clones.length; i++)
            this._clones[i].show();
    }

    /**
     * hide:
     * Hide the crosshairs.
     */
    hide() {
        this._actor.hide();
        // Clones don't share visibility.
        for (let i = 0; i < this._clones.length; i++)
            this._clones[i].hide();
    }

    /**
     * reCenter:
     * Reposition the horizontal and vertical hairs such that they cross at
     * the center of crosshairs group.  If called with the dimensions of
     * the clip rectangle, these are used to update the size of the clip.
     * @clipSize:  Optional.  If present, an array of the form [width, height].
     */
    reCenter(clipSize) {
        let [groupWidth, groupHeight] = this._actor.get_size();
        let leftLength = this._horizLeftHair.get_width();
        let rightLength = this._horizRightHair.get_width();
        let topLength = this._vertTopHair.get_height();
        let bottomLength = this._vertBottomHair.get_height();
        let thickness = this._horizLeftHair.get_height();

        // Deal with clip rectangle.
        if (clipSize)
            this._clipSize = clipSize;
        let clipWidth = this._clipSize[0];
        let clipHeight = this._clipSize[1];

        // Note that clip, if present, is not centred on the cross hair
        // intersection, but biased towards the top left.
        let left = groupWidth / 2 - clipWidth * 0.25 - leftLength;
        let right = groupWidth / 2 + clipWidth * 0.75;
        let top = groupHeight / 2 - clipHeight * 0.25 - topLength - thickness / 2;
        let bottom = groupHeight / 2 + clipHeight * 0.75 + thickness / 2;
        this._horizLeftHair.set_position(left, (groupHeight - thickness) / 2);
        this._horizRightHair.set_position(right, (groupHeight - thickness) / 2);
        this._vertTopHair.set_position((groupWidth - thickness) / 2, top);
        this._vertBottomHair.set_position((groupWidth - thickness) / 2, bottom);
    }
};

var MagShaderEffects = class MagShaderEffects {
    constructor(uiGroupClone) {
        this._inverse = new Shell.InvertLightnessEffect();
        this._brightnessContrast = new Clutter.BrightnessContrastEffect();
        this._colorDesaturation = new Clutter.DesaturateEffect();
        this._inverse.set_enabled(false);
        this._brightnessContrast.set_enabled(false);

        this._magView = uiGroupClone;
        this._magView.add_effect(this._inverse);
        this._magView.add_effect(this._brightnessContrast);
        this._magView.add_effect(this._colorDesaturation);
    }

    /**
     * destroyEffects:
     * Remove contrast and brightness effects from the magnified view, and
     * lose the reference to the actor they were applied to.  Don't use this
     * object after calling this.
     */
    destroyEffects() {
        this._magView.clear_effects();
        this._colorDesaturation = null;
        this._brightnessContrast = null;
        this._inverse = null;
        this._magView = null;
    }

    /**
     * setInvertLightness:
     * Enable/disable invert lightness effect.
     * @invertFlag:     Enabled flag.
     */
    setInvertLightness(invertFlag) {
        this._inverse.set_enabled(invertFlag);
    }

    setColorSaturation(factor) {
        this._colorDesaturation.set_factor(1.0 - factor);
    }

    /**
     * setBrightness:
     * Set the brightness of the magnified view.
     * @brightness: Object containing the brightness for the red, green,
     *              and blue channels.  Values of 0.0 represent "standard"
     *              brightness (no change), whereas values less or greater than
     *              0.0 indicate decreased or incresaed brightness,
     *              respectively.
     */
    setBrightness(brightness) {
        let bRed = brightness.r;
        let bGreen = brightness.g;
        let bBlue = brightness.b;
        this._brightnessContrast.set_brightness_full(bRed, bGreen, bBlue);

        // Enable the effect if the brightness OR contrast change are such that
        // it modifies the brightness and/or contrast.
        let [cRed, cGreen, cBlue] = this._brightnessContrast.get_contrast();
        this._brightnessContrast.set_enabled(
            (bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE ||
             cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE)
        );
    }

    /**
     * Set the contrast of the magnified view.
     * @contrast:   Object containing the contrast for the red, green,
     *              and blue channels.  Values of 0.0 represent "standard"
     *              contrast (no change), whereas values less or greater than
     *              0.0 indicate decreased or incresaed contrast, respectively.
     */
    setContrast(contrast) {
        let cRed = contrast.r;
        let cGreen = contrast.g;
        let cBlue = contrast.b;

        this._brightnessContrast.set_contrast_full(cRed, cGreen, cBlue);

        // Enable the effect if the contrast OR brightness change are such that
        // it modifies the brightness and/or contrast.
        // should be able to use Clutter.color_equal(), but that complains of
        // a null first argument.
        let [bRed, bGreen, bBlue] = this._brightnessContrast.get_brightness();
        this._brightnessContrast.set_enabled(
             cRed != NO_CHANGE || cGreen != NO_CHANGE || cBlue != NO_CHANGE ||
             bRed != NO_CHANGE || bGreen != NO_CHANGE || bBlue != NO_CHANGE
        );
    }
};
(uuay)messageList.js_const { Atk, Clutter, Gio, GLib, GObject, Meta, Pango, St } = imports.gi;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Signals = imports.signals;

const Calendar = imports.ui.calendar;
const Tweener = imports.ui.tweener;
const Util = imports.misc.util;

var MESSAGE_ANIMATION_TIME = 0.1;

var DEFAULT_EXPAND_LINES = 6;

function _fixMarkup(text, allowMarkup) {
    if (allowMarkup) {
        // Support &amp;, &quot;, &apos;, &lt; and &gt;, escape all other
        // occurrences of '&'.
        let _text = text.replace(/&(?!amp;|quot;|apos;|lt;|gt;)/g, '&amp;');

        // Support <b>, <i>, and <u>, escape anything else
        // so it displays as raw markup.
        // Ref: https://developer.gnome.org/notification-spec/#markup
        _text = _text.replace(/<(?!\/?[biu]>)/g, '&lt;');

        try {
            Pango.parse_markup(_text, -1, '');
            return _text;
        } catch (e) {}
    }

    // !allowMarkup, or invalid markup
    return GLib.markup_escape_text(text, -1);
}

var URLHighlighter = class URLHighlighter {
    constructor(text, lineWrap, allowMarkup) {
        if (!text)
            text = '';
        this.actor = new St.Label({ reactive: true, style_class: 'url-highlighter',
                                    x_expand: true, x_align: Clutter.ActorAlign.START });
        this._linkColor = '#ccccff';
        this.actor.connect('style-changed', () => {
            let [hasColor, color] = this.actor.get_theme_node().lookup_color('link-color', false);
            if (hasColor) {
                let linkColor = color.to_string().substr(0, 7);
                if (linkColor != this._linkColor) {
                    this._linkColor = linkColor;
                    this._highlightUrls();
                }
            }
        });
        this.actor.clutter_text.line_wrap = lineWrap;
        this.actor.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;

        this.setMarkup(text, allowMarkup);
        this.actor.connect('button-press-event', (actor, event) => {
            // Don't try to URL highlight when invisible.
            // The MessageTray doesn't actually hide us, so
            // we need to check for paint opacities as well.
            if (!actor.visible || actor.get_paint_opacity() == 0)
                return Clutter.EVENT_PROPAGATE;

            // Keep Notification.actor from seeing this and taking
            // a pointer grab, which would block our button-release-event
            // handler, if an URL is clicked
            return this._findUrlAtPos(event) != -1;
        });
        this.actor.connect('button-release-event', (actor, event) => {
            if (!actor.visible || actor.get_paint_opacity() == 0)
                return Clutter.EVENT_PROPAGATE;

            let urlId = this._findUrlAtPos(event);
            if (urlId != -1) {
                let url = this._urls[urlId].url;
                if (url.indexOf(':') == -1)
                    url = 'http://' + url;

                Gio.app_info_launch_default_for_uri(url, global.create_app_launch_context(0, -1));
                return Clutter.EVENT_STOP;
            }
            return Clutter.EVENT_PROPAGATE;
        });
        this.actor.connect('motion-event', (actor, event) => {
            if (!actor.visible || actor.get_paint_opacity() == 0)
                return Clutter.EVENT_PROPAGATE;

            let urlId = this._findUrlAtPos(event);
            if (urlId != -1 && !this._cursorChanged) {
                global.display.set_cursor(Meta.Cursor.POINTING_HAND);
                this._cursorChanged = true;
            } else if (urlId == -1) {
                global.display.set_cursor(Meta.Cursor.DEFAULT);
                this._cursorChanged = false;
            }
            return Clutter.EVENT_PROPAGATE;
        });
        this.actor.connect('leave-event', () => {
            if (!this.actor.visible || this.actor.get_paint_opacity() == 0)
                return Clutter.EVENT_PROPAGATE;

            if (this._cursorChanged) {
                this._cursorChanged = false;
                global.display.set_cursor(Meta.Cursor.DEFAULT);
            }
            return Clutter.EVENT_PROPAGATE;
        });
    }

    setMarkup(text, allowMarkup) {
        text = text ? _fixMarkup(text, allowMarkup) : '';
        this._text = text;

        this.actor.clutter_text.set_markup(text);
        /* clutter_text.text contain text without markup */
        this._urls = Util.findUrls(this.actor.clutter_text.text);
        this._highlightUrls();
    }

    _highlightUrls() {
        // text here contain markup
        let urls = Util.findUrls(this._text);
        let markup = '';
        let pos = 0;
        for (let i = 0; i < urls.length; i++) {
            let url = urls[i];
            let str = this._text.substr(pos, url.pos - pos);
            markup += str + '<span foreground="' + this._linkColor + '"><u>' + url.url + '</u></span>';
            pos = url.pos + url.url.length;
        }
        markup += this._text.substr(pos);
        this.actor.clutter_text.set_markup(markup);
    }

    _findUrlAtPos(event) {
        let success;
        let [x, y] = event.get_coords();
        [success, x, y] = this.actor.transform_stage_point(x, y);
        let find_pos = -1;
        for (let i = 0; i < this.actor.clutter_text.text.length; i++) {
            let [success, px, py, line_height] = this.actor.clutter_text.position_to_coords(i);
            if (py > y || py + line_height < y || x < px)
                continue;
            find_pos = i;
        }
        if (find_pos != -1) {
            for (let i = 0; i < this._urls.length; i++)
            if (find_pos >= this._urls[i].pos &&
                this._urls[i].pos + this._urls[i].url.length > find_pos)
                return i;
        }
        return -1;
    }
};

var ScaleLayout = GObject.registerClass(
class ScaleLayout extends Clutter.BinLayout {
    _init(params) {
        this._container = null;
        super._init(params);
    }

    _connectContainer(container) {
        if (this._container == container)
            return;

        if (this._container)
            for (let id of this._signals)
                this._container.disconnect(id);

        this._container = container;
        this._signals = [];

        if (this._container)
            for (let signal of ['notify::scale-x', 'notify::scale-y']) {
                let id = this._container.connect(signal, () => {
                    this.layout_changed();
                });
                this._signals.push(id);
            }
    }

    vfunc_get_preferred_width(container, forHeight) {
        this._connectContainer(container);

        let [min, nat] = super.vfunc_get_preferred_width(container, forHeight);
        return [Math.floor(min * container.scale_x),
                Math.floor(nat * container.scale_x)];
    }

    vfunc_get_preferred_height(container, forWidth) {
        this._connectContainer(container);

        let [min, nat] = super.vfunc_get_preferred_height(container, forWidth);
        return [Math.floor(min * container.scale_y),
                Math.floor(nat * container.scale_y)];
    }
});

var LabelExpanderLayout = GObject.registerClass({
    Properties: { 'expansion': GObject.ParamSpec.double('expansion',
                                                        'Expansion',
                                                        'Expansion of the layout, between 0 (collapsed) ' +
                                                        'and 1 (fully expanded',
                                                         GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
                                                         0, 1, 0)},
}, class LabelExpanderLayout extends Clutter.LayoutManager {
    _init(params) {
        this._expansion = 0;
        this._expandLines = DEFAULT_EXPAND_LINES;

        super._init(params);
    }

    get expansion() {
        return this._expansion;
    }

    set expansion(v) {
        if (v == this._expansion)
            return;
        this._expansion = v;
        this.notify('expansion');

        let visibleIndex = this._expansion > 0 ? 1 : 0;
        for (let i = 0; this._container && i < this._container.get_n_children(); i++)
            this._container.get_child_at_index(i).visible = (i == visibleIndex);

        this.layout_changed();
    }

    set expandLines(v) {
        if (v == this._expandLines)
            return;
        this._expandLines = v;
        if (this._expansion > 0)
            this.layout_changed();
    }

    vfunc_set_container(container) {
        this._container = container;
    }

    vfunc_get_preferred_width(container, forHeight) {
        let [min, nat] = [0, 0];

        for (let i = 0; i < container.get_n_children(); i++) {
            if (i > 1)
                break; // we support one unexpanded + one expanded child

            let child = container.get_child_at_index(i);
            let [childMin, childNat] = child.get_preferred_width(forHeight);
            [min, nat] = [Math.max(min, childMin), Math.max(nat, childNat)];
        }

        return [min, nat];
    }

    vfunc_get_preferred_height(container, forWidth) {
        let [min, nat] = [0, 0];

        let children = container.get_children();
        if (children[0])
            [min, nat] = children[0].get_preferred_height(forWidth);

        if (children[1]) {
            let [min2, nat2] = children[1].get_preferred_height(forWidth);
            let [expMin, expNat] = [Math.min(min2, min * this._expandLines),
                                    Math.min(nat2, nat * this._expandLines)];
            [min, nat] = [min + this._expansion * (expMin - min),
                          nat + this._expansion * (expNat - nat)];
        }

        return [min, nat];
    }

    vfunc_allocate(container, box, flags) {
        for (let i = 0; i < container.get_n_children(); i++) {
            let child = container.get_child_at_index(i);

            if (child.visible)
                child.allocate(box, flags);
        }

    }
});

var Message = class Message {
    constructor(title, body) {
        this.expanded = false;

        this._useBodyMarkup = false;

        this.actor = new St.Button({ style_class: 'message',
                                     accessible_role: Atk.Role.NOTIFICATION,
                                     can_focus: true,
                                     x_expand: true, x_fill: true });
        this.actor.connect('key-press-event',
                           this._onKeyPressed.bind(this));

        let vbox = new St.BoxLayout({ vertical: true });
        this.actor.set_child(vbox);

        let hbox = new St.BoxLayout();
        vbox.add_actor(hbox);

        this._actionBin = new St.Widget({ layout_manager: new ScaleLayout(),
                                          visible: false });
        vbox.add_actor(this._actionBin);

        this._iconBin = new St.Bin({ style_class: 'message-icon-bin',
                                     y_expand: true,
                                     y_align: St.Align.START,
                                     visible: false });
        hbox.add_actor(this._iconBin);

        let contentBox = new St.BoxLayout({ style_class: 'message-content',
                                            vertical: true, x_expand: true });
        hbox.add_actor(contentBox);

        this._mediaControls = new St.BoxLayout();
        hbox.add_actor(this._mediaControls);

        let titleBox = new St.BoxLayout();
        contentBox.add_actor(titleBox);

        this.titleLabel = new St.Label({ style_class: 'message-title' });
        this.setTitle(title);
        titleBox.add_actor(this.titleLabel);

        this._secondaryBin = new St.Bin({ style_class: 'message-secondary-bin',
                                          x_expand: true, y_expand: true,
                                          x_fill: true, y_fill: true });
        titleBox.add_actor(this._secondaryBin);

        let closeIcon = new St.Icon({ icon_name: 'window-close-symbolic',
                                      icon_size: 16 });
        this._closeButton = new St.Button({ child: closeIcon, opacity: 0 });
        titleBox.add_actor(this._closeButton);

        this._bodyStack = new St.Widget({ x_expand: true });
        this._bodyStack.layout_manager = new LabelExpanderLayout();
        contentBox.add_actor(this._bodyStack);

        this.bodyLabel = new URLHighlighter('', false, this._useBodyMarkup);
        this.bodyLabel.actor.add_style_class_name('message-body');
        this._bodyStack.add_actor(this.bodyLabel.actor);
        this.setBody(body);

        this._closeButton.connect('clicked', this.close.bind(this));
        let actorHoverId = this.actor.connect('notify::hover', this._sync.bind(this));
        this._closeButton.connect('destroy', this.actor.disconnect.bind(this.actor, actorHoverId));
        this.actor.connect('clicked', this._onClicked.bind(this));
        this.actor.connect('destroy', this._onDestroy.bind(this));
        this._sync();
    }

    close() {
        this.emit('close');
    }

    setIcon(actor) {
        this._iconBin.child = actor;
        this._iconBin.visible = (actor != null);
    }

    setSecondaryActor(actor) {
        this._secondaryBin.child = actor;
    }

    setTitle(text) {
        let title = text ? _fixMarkup(text.replace(/\n/g, ' '), false) : '';
        this.titleLabel.clutter_text.set_markup(title);
    }

    setBody(text) {
        this._bodyText = text;
        this.bodyLabel.setMarkup(text ? text.replace(/\n/g, ' ') : '',
                                 this._useBodyMarkup);
        if (this._expandedLabel)
            this._expandedLabel.setMarkup(text, this._useBodyMarkup);
    }

    setUseBodyMarkup(enable) {
        if (this._useBodyMarkup === enable)
            return;
        this._useBodyMarkup = enable;
        if (this.bodyLabel)
            this.setBody(this._bodyText);
    }

    setActionArea(actor) {
        if (actor == null) {
            if (this._actionBin.get_n_children() > 0)
                this._actionBin.get_child_at_index(0).destroy();
            return;
        }

        if (this._actionBin.get_n_children() > 0)
            throw new Error('Message already has an action area');

        this._actionBin.add_actor(actor);
        this._actionBin.visible = this.expanded;
    }

    addMediaControl(iconName, callback) {
        let icon = new St.Icon({ icon_name: iconName, icon_size: 16 });
        let button = new St.Button({ style_class: 'message-media-control',
                                     child: icon });
        button.connect('clicked', callback);
        this._mediaControls.add_actor(button);
        return button;
    }

    setExpandedBody(actor) {
        if (actor == null) {
            if (this._bodyStack.get_n_children() > 1)
                this._bodyStack.get_child_at_index(1).destroy();
            return;
        }

        if (this._bodyStack.get_n_children() > 1)
            throw new Error('Message already has an expanded body actor');

        this._bodyStack.insert_child_at_index(actor, 1);
    }

    setExpandedLines(nLines) {
        this._bodyStack.layout_manager.expandLines = nLines;
    }

    expand(animate) {
        this.expanded = true;

        this._actionBin.visible = (this._actionBin.get_n_children() > 0);

        if (this._bodyStack.get_n_children() < 2) {
            this._expandedLabel = new URLHighlighter(this._bodyText,
                                                     true, this._useBodyMarkup);
            this.setExpandedBody(this._expandedLabel.actor);
        }

        if (animate) {
            Tweener.addTween(this._bodyStack.layout_manager,
                             { expansion: 1,
                               time: MessageTray.ANIMATION_TIME,
                               transition: 'easeOutQuad' });
            this._actionBin.scale_y = 0;
            Tweener.addTween(this._actionBin,
                             { scale_y: 1,
                               time: MessageTray.ANIMATION_TIME,
                               transition: 'easeOutQuad' });
        } else {
            this._bodyStack.layout_manager.expansion = 1;
            this._actionBin.scale_y = 1;
        }

        this.emit('expanded');
    }

    unexpand(animate) {
        if (animate) {
            Tweener.addTween(this._bodyStack.layout_manager,
                             { expansion: 0,
                               time: MessageTray.ANIMATION_TIME,
                               transition: 'easeOutQuad' });
            Tweener.addTween(this._actionBin,
                             { scale_y: 0,
                               time: MessageTray.ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onCompleteScope: this,
                               onComplete() {
                                   this._actionBin.hide();
                                   this.expanded = false;
                               }});
        } else {
            this._bodyStack.layout_manager.expansion = 0;
            this._actionBin.scale_y = 0;
            this.expanded = false;
        }

        this.emit('unexpanded');
    }

    canClose() {
        return false;
    }

    _sync() {
        let visible = this.actor.hover && this.canClose();
        this._closeButton.opacity = visible ? 255 : 0;
        this._closeButton.reactive = visible;
    }

    _onClicked() {
    }

    _onDestroy() {
    }

    _onKeyPressed(a, event) {
        let keysym = event.get_key_symbol();

        if (keysym == Clutter.KEY_Delete ||
            keysym == Clutter.KEY_KP_Delete) {
            this.close();
            return Clutter.EVENT_STOP;
        }
        return Clutter.EVENT_PROPAGATE;
    }
};
Signals.addSignalMethods(Message.prototype);

var MessageListSection = class MessageListSection {
    constructor() {
        this.actor = new St.BoxLayout({ style_class: 'message-list-section',
                                        clip_to_allocation: true,
                                        x_expand: true, vertical: true });

        this._list = new St.BoxLayout({ style_class: 'message-list-section-list',
                                        vertical: true });
        this.actor.add_actor(this._list);

        this._list.connect('actor-added', this._sync.bind(this));
        this._list.connect('actor-removed', this._sync.bind(this));

        let id = Main.sessionMode.connect('updated',
                                          this._sync.bind(this));
        this.actor.connect('destroy', () => {
            Main.sessionMode.disconnect(id);
        });

        this._messages = new Map();
        this._date = new Date();
        this.empty = true;
        this.canClear = false;
        this._sync();
    }

    _onKeyFocusIn(actor) {
        this.emit('key-focus-in', actor);
    }

    get allowed() {
        return true;
    }

    setDate(date) {
        if (Calendar.sameDay(date, this._date))
            return;
        this._date = date;
        this._sync();
    }

    addMessage(message, animate) {
        this.addMessageAtIndex(message, -1, animate);
    }

    addMessageAtIndex(message, index, animate) {
        let obj = {
            container: null,
            destroyId: 0,
            keyFocusId: 0,
            closeId: 0
        };
        let pivot = new Clutter.Point({ x: .5, y: .5 });
        let scale = animate ? 0 : 1;
        obj.container = new St.Widget({ layout_manager: new ScaleLayout(),
                                        pivot_point: pivot,
                                        scale_x: scale, scale_y: scale });
        obj.keyFocusId = message.actor.connect('key-focus-in',
            this._onKeyFocusIn.bind(this));
        obj.destroyId = message.actor.connect('destroy', () => {
            this.removeMessage(message, false);
        });
        obj.closeId = message.connect('close', () => {
            this.removeMessage(message, true);
        });

        this._messages.set(message, obj);
        obj.container.add_actor(message.actor);

        this._list.insert_child_at_index(obj.container, index);

        if (animate)
            Tweener.addTween(obj.container, { scale_x: 1,
                                              scale_y: 1,
                                              time: MESSAGE_ANIMATION_TIME,
                                              transition: 'easeOutQuad' });
    }

    moveMessage(message, index, animate) {
        let obj = this._messages.get(message);

        if (!animate) {
            this._list.set_child_at_index(obj.container, index);
            return;
        }

        let onComplete = () => {
            this._list.set_child_at_index(obj.container, index);
            Tweener.addTween(obj.container, { scale_x: 1,
                                              scale_y: 1,
                                              time: MESSAGE_ANIMATION_TIME,
                                              transition: 'easeOutQuad' });
        };
        Tweener.addTween(obj.container, { scale_x: 0,
                                          scale_y: 0,
                                          time: MESSAGE_ANIMATION_TIME,
                                          transition: 'easeOutQuad',
                                          onComplete: onComplete });
    }

    removeMessage(message, animate) {
        let obj = this._messages.get(message);

        message.actor.disconnect(obj.destroyId);
        message.actor.disconnect(obj.keyFocusId);
        message.disconnect(obj.closeId);

        this._messages.delete(message);

        if (animate) {
            Tweener.addTween(obj.container, { scale_x: 0, scale_y: 0,
                                              time: MESSAGE_ANIMATION_TIME,
                                              transition: 'easeOutQuad',
                                              onComplete() {
                                                  obj.container.destroy();
                                                  global.sync_pointer();
                                              }});
        } else {
            obj.container.destroy();
            global.sync_pointer();
        }
    }

    clear() {
        let messages = [...this._messages.keys()].filter(msg => msg.canClose());

        // If there are few messages, letting them all zoom out looks OK
        if (messages.length < 2) {
            messages.forEach(message => {
                message.close();
            });
        } else {
            // Otherwise we slide them out one by one, and then zoom them
            // out "off-screen" in the end to smoothly shrink the parent
            let delay = MESSAGE_ANIMATION_TIME / Math.max(messages.length, 5);
            for (let i = 0; i < messages.length; i++) {
                let message = messages[i];
                let obj = this._messages.get(message);
                Tweener.addTween(obj.container,
                                 { anchor_x: this._list.width,
                                   opacity: 0,
                                   time: MESSAGE_ANIMATION_TIME,
                                   delay: i * delay,
                                   transition: 'easeOutQuad',
                                   onComplete() {
                                       message.close();
                                   }});
            }
        }
    }

    _canClear() {
        for (let message of this._messages.keys())
            if (message.canClose())
                return true;
        return false;
    }

    _shouldShow() {
        return !this.empty;
    }

    _sync() {
        let empty = this._list.get_n_children() == 0;
        let changed = this.empty !== empty;
        this.empty = empty;

        if (changed)
            this.emit('empty-changed');

        let canClear = this._canClear();
        changed = this.canClear !== canClear;
        this.canClear = canClear;

        if (changed)
            this.emit('can-clear-changed');

        this.actor.visible = this.allowed && this._shouldShow();
    }
};
Signals.addSignalMethods(MessageListSection.prototype);
(uuay)config.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

/* The name of this package (not localized) */
var PACKAGE_NAME = 'gnome-shell';
/* The version of this package */
var PACKAGE_VERSION = '3.32.2';
/* 1 if gnome-bluetooth is available, 0 otherwise */
var HAVE_BLUETOOTH = 1;
/* 1 if networkmanager is available, 0 otherwise */
var HAVE_NETWORKMANAGER = 1;
/* 1 if portal helper is enabled, 0 otherwise */
var HAVE_PORTAL_HELPER = 0;
/* gettext package */
var GETTEXT_PACKAGE = 'gnome-shell';
/* locale dir */
var LOCALEDIR = '/usr/share/locale';
/* other standard directories */
var LIBEXECDIR = '/usr/libexec';
var PKGDATADIR = '/usr/share/gnome-shell';
var VPNDIR = '/usr/lib/NetworkManager/VPN';
/* g-i package versions */
var LIBMUTTER_API_VERSION = '4'
(uuay)org/unlockDialog.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { AccountsService, Atk, Clutter,
        Gdm, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;

const Layout = imports.ui.layout;
const Main = imports.ui.main;

const AuthPrompt = imports.gdm.authPrompt;

// The timeout before going back automatically to the lock screen (in seconds)
const IDLE_TIMEOUT = 2 * 60;

var UnlockDialog = GObject.registerClass({
    Signals: { 'failed': {} },
}, class UnlockDialog extends St.Widget {
    _init(parentActor) {
        super._init({
            accessible_role: Atk.Role.WINDOW,
            style_class: 'login-dialog',
            layout_manager: new Clutter.BoxLayout(),
            visible: false,
        });

        this.add_constraint(new Layout.MonitorConstraint({ primary: true }));
        parentActor.add_child(this);

        this._userManager = AccountsService.UserManager.get_default();
        this._userName = GLib.get_user_name();
        this._user = this._userManager.get_user(this._userName);

        this._promptBox = new St.BoxLayout({ vertical: true,
                                             x_align: Clutter.ActorAlign.CENTER,
                                             y_align: Clutter.ActorAlign.CENTER,
                                             x_expand: true,
                                             y_expand: true });
        this.add_child(this._promptBox);

        this._gdmClient = new Gdm.Client();

        try {
            this._gdmClient.set_enabled_extensions([Gdm.UserVerifierChoiceList.interface_info().name]);
        } catch(e) {
        }

        this._authPrompt = new AuthPrompt.AuthPrompt(this._gdmClient, AuthPrompt.AuthPromptMode.UNLOCK_ONLY);
        this._authPrompt.connect('failed', this._fail.bind(this));
        this._authPrompt.connect('cancelled', this._fail.bind(this));
        this._authPrompt.connect('reset', this._onReset.bind(this));
        this._authPrompt.setPasswordChar('\u25cf');
        this._authPrompt.nextButton.label = _("Unlock");

        this._promptBox.add_child(this._authPrompt.actor);

        this.allowCancel = false;

        let screenSaverSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.screensaver' });
        if (screenSaverSettings.get_boolean('user-switch-enabled')) {
            let otherUserLabel = new St.Label({ text: _("Log in as another user"),
                                                style_class: 'login-dialog-not-listed-label' });
            this._otherUserButton = new St.Button({ style_class: 'login-dialog-not-listed-button',
                                                    can_focus: true,
                                                    child: otherUserLabel,
                                                    reactive: true,
                                                    x_align: St.Align.START,
                                                    x_fill: false });
            this._otherUserButton.connect('clicked', this._otherUserClicked.bind(this));
            this._promptBox.add_child(this._otherUserButton);
        } else {
            this._otherUserButton = null;
        }

        this._authPrompt.reset();
        this._updateSensitivity(true);

        Main.ctrlAltTabManager.addGroup(this, _("Unlock Window"), 'dialog-password-symbolic');

        this._idleMonitor = Meta.IdleMonitor.get_core();
        this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, this._escape.bind(this));

        this.connect('destroy', this._onDestroy.bind(this));
    }

    _updateSensitivity(sensitive) {
        this._authPrompt.updateSensitivity(sensitive);

        if (this._otherUserButton) {
            this._otherUserButton.reactive = sensitive;
            this._otherUserButton.can_focus = sensitive;
        }
    }

    _fail() {
        this.emit('failed');
    }

    _onReset(authPrompt, beginRequest) {
        let userName;
        if (beginRequest == AuthPrompt.BeginRequestType.PROVIDE_USERNAME) {
            this._authPrompt.setUser(this._user);
            userName = this._userName;
        } else {
            userName = null;
        }

        this._authPrompt.begin({ userName: userName });
    }

    _escape() {
        if (this.allowCancel)
            this._authPrompt.cancel();
    }

    _otherUserClicked(button, event) {
        Gdm.goto_login_session_sync(null);

        this._authPrompt.cancel();
    }

    _onDestroy() {
        this.popModal();

        if (this._idleWatchId) {
            this._idleMonitor.remove_watch(this._idleWatchId);
            this._idleWatchId = 0;
        }
    }

    cancel() {
        this._authPrompt.cancel();

        this.destroy();
    }

    addCharacter(unichar) {
        this._authPrompt.addCharacter(unichar);
    }

    finish(onComplete) {
        this._authPrompt.finish(onComplete);
    }

    open(timestamp) {
        this.show();

        if (this._isModal)
            return true;

        let modalParams = {
            timestamp,
            actionMode: Shell.ActionMode.UNLOCK_SCREEN,
        };
        if (!Main.pushModal(this, modalParams))
            return false;

        this._isModal = true;

        return true;
    }

    popModal(timestamp) {
        if (this._isModal) {
            Main.popModal(this, timestamp);
            this._isModal = false;
        }
    }
});
(uuay)util.js=E// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Shell, St } = imports.gi;
const Gettext = imports.gettext;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const Params = imports.misc.params;

var SCROLL_TIME = 0.1;

// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
const _balancedParens = '\\([^\\s()<>]+\\)';
const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]';
const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u201C\u201D\u2018\u2019]';

const _urlRegexp = new RegExp(
    '(^|' + _leadingJunk + ')' +
    '(' +
        '(?:' +
            '(?:http|https|ftp)://' +             // scheme://
            '|' +
            'www\\d{0,3}[.]' +                    // www.
            '|' +
            '[a-z0-9.\\-]+[.][a-z]{2,4}/' +       // foo.xx/
        ')' +
        '(?:' +                                   // one or more:
            '[^\\s()<>]+' +                       // run of non-space non-()
            '|' +                                 // or
            _balancedParens +                     // balanced parens
        ')+' +
        '(?:' +                                   // end with:
            _balancedParens +                     // balanced parens
            '|' +                                 // or
            _notTrailingJunk +                    // last non-junk char
        ')' +
    ')', 'gi');

let _desktopSettings = null;

// findUrls:
// @str: string to find URLs in
//
// Searches @str for URLs and returns an array of objects with %url
// properties showing the matched URL string, and %pos properties indicating
// the position within @str where the URL was found.
//
// Return value: the list of match objects, as described above
function findUrls(str) {
    let res = [], match;
    while ((match = _urlRegexp.exec(str)))
        res.push({ url: match[2], pos: match.index + match[1].length });
    return res;
}

// spawn:
// @argv: an argv array
//
// Runs @argv in the background, handling any errors that occur
// when trying to start the program.
function spawn(argv) {
    try {
        trySpawn(argv);
    } catch (err) {
        _handleSpawnError(argv[0], err);
    }
}

// spawnCommandLine:
// @command_line: a command line
//
// Runs @command_line in the background, handling any errors that
// occur when trying to parse or start the program.
function spawnCommandLine(command_line) {
    try {
        let [success, argv] = GLib.shell_parse_argv(command_line);
        trySpawn(argv);
    } catch (err) {
        _handleSpawnError(command_line, err);
    }
}

// spawnApp:
// @argv: an argv array
//
// Runs @argv as if it was an application, handling startup notification
function spawnApp(argv) {
    try {
        let app = Gio.AppInfo.create_from_commandline(argv.join(' '), null,
                                                      Gio.AppInfoCreateFlags.SUPPORTS_STARTUP_NOTIFICATION);

        let context = global.create_app_launch_context(0, -1);
        app.launch([], context);
    } catch(err) {
        _handleSpawnError(argv[0], err);
    }
}

// trySpawn:
// @argv: an argv array
//
// Runs @argv in the background. If launching @argv fails,
// this will throw an error.
function trySpawn(argv)
{
    var success, pid;
    try {
        [success, pid] = GLib.spawn_async(null, argv, null,
                                          GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                          null);
    } catch (err) {
        /* Rewrite the error in case of ENOENT */
        if (err.matches(GLib.SpawnError, GLib.SpawnError.NOENT)) {
            throw new GLib.SpawnError({ code: GLib.SpawnError.NOENT,
                                        message: _("Command not found") });
        } else if (err instanceof GLib.Error) {
            // The exception from gjs contains an error string like:
            //   Error invoking GLib.spawn_command_line_async: Failed to
            //   execute child process "foo" (No such file or directory)
            // We are only interested in the part in the parentheses. (And
            // we can't pattern match the text, since it gets localized.)
            let message = err.message.replace(/.*\((.+)\)/, '$1');
            throw new (err.constructor)({ code: err.code,
                                          message: message });
        } else {
            throw err;
        }
    }
    // Dummy child watch; we don't want to double-fork internally
    // because then we lose the parent-child relationship, which
    // can break polkit.  See https://bugzilla.redhat.com//show_bug.cgi?id=819275
    GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, () => {});
}

// trySpawnCommandLine:
// @command_line: a command line
//
// Runs @command_line in the background. If launching @command_line
// fails, this will throw an error.
function trySpawnCommandLine(command_line) {
    let success, argv;

    try {
        [success, argv] = GLib.shell_parse_argv(command_line);
    } catch (err) {
        // Replace "Error invoking GLib.shell_parse_argv: " with
        // something nicer
        err.message = err.message.replace(/[^:]*: /, _("Could not parse command:") + "\n");
        throw err;
    }

    trySpawn(argv);
}

function _handleSpawnError(command, err) {
    let title = _("Execution of “%s” failed:").format(command);
    Main.notifyError(title, err.message);
}

function formatTimeSpan(date) {
    let now = GLib.DateTime.new_now_local();

    let timespan = now.difference(date);

    let minutesAgo = timespan / GLib.TIME_SPAN_MINUTE;
    let hoursAgo = timespan / GLib.TIME_SPAN_HOUR;
    let daysAgo = timespan / GLib.TIME_SPAN_DAY;
    let weeksAgo = daysAgo / 7;
    let monthsAgo = daysAgo / 30;
    let yearsAgo = weeksAgo / 52;

    if (minutesAgo < 5)
        return _("Just now");
    if (hoursAgo < 1)
        return Gettext.ngettext("%d minute ago",
                                "%d minutes ago", minutesAgo).format(minutesAgo);
    if (daysAgo < 1)
        return Gettext.ngettext("%d hour ago",
                                "%d hours ago", hoursAgo).format(hoursAgo);
    if (daysAgo < 2)
        return _("Yesterday");
    if (daysAgo < 15)
        return Gettext.ngettext("%d day ago",
                                "%d days ago", daysAgo).format(daysAgo);
    if (weeksAgo < 8)
        return Gettext.ngettext("%d week ago",
                                "%d weeks ago", weeksAgo).format(weeksAgo);
    if (yearsAgo < 1)
        return Gettext.ngettext("%d month ago",
                                "%d months ago", monthsAgo).format(monthsAgo);
    return Gettext.ngettext("%d year ago",
                            "%d years ago", yearsAgo).format(yearsAgo);
}

function formatTime(time, params) {
    let date;
    // HACK: The built-in Date type sucks at timezones, which we need for the
    //       world clock; it's often more convenient though, so allow either
    //       Date or GLib.DateTime as parameter
    if (time instanceof Date)
        date = GLib.DateTime.new_from_unix_local(time.getTime() / 1000);
    else
        date = time;

    let now = GLib.DateTime.new_now_local();

    let daysAgo = now.difference(date) / (24 * 60 * 60 * 1000 * 1000);

    let format;

    if (_desktopSettings == null)
        _desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
    let clockFormat = _desktopSettings.get_string('clock-format');

    params = Params.parse(params, { timeOnly: false });

    if (clockFormat == '24h') {
        // Show only the time if date is on today
        if (daysAgo < 1 || params.timeOnly)
            /* Translators: Time in 24h format */
            format = N_("%H\u2236%M");
        // Show the word "Yesterday" and time if date is on yesterday
        else if (daysAgo <2)
            /* Translators: this is the word "Yesterday" followed by a
             time string in 24h format. i.e. "Yesterday, 14:30" */
            // xgettext:no-c-format
            format = N_("Yesterday, %H\u2236%M");
        // Show a week day and time if date is in the last week
        else if (daysAgo < 7)
            /* Translators: this is the week day name followed by a time
             string in 24h format. i.e. "Monday, 14:30" */
            // xgettext:no-c-format
            format = N_("%A, %H\u2236%M");
        else if (date.get_year() == now.get_year())
            /* Translators: this is the month name and day number
             followed by a time string in 24h format.
             i.e. "May 25, 14:30" */
            // xgettext:no-c-format
            format = N_("%B %-d, %H\u2236%M");
        else
            /* Translators: this is the month name, day number, year
             number followed by a time string in 24h format.
             i.e. "May 25 2012, 14:30" */
            // xgettext:no-c-format
            format = N_("%B %-d %Y, %H\u2236%M");
    } else {
        // Show only the time if date is on today
        if (daysAgo < 1 || params.timeOnly)
            /* Translators: Time in 12h format */
            format = N_("%l\u2236%M %p");
        // Show the word "Yesterday" and time if date is on yesterday
        else if (daysAgo <2)
            /* Translators: this is the word "Yesterday" followed by a
             time string in 12h format. i.e. "Yesterday, 2:30 pm" */
            // xgettext:no-c-format
            format = N_("Yesterday, %l\u2236%M %p");
        // Show a week day and time if date is in the last week
        else if (daysAgo < 7)
            /* Translators: this is the week day name followed by a time
             string in 12h format. i.e. "Monday, 2:30 pm" */
            // xgettext:no-c-format
            format = N_("%A, %l\u2236%M %p");
        else if (date.get_year() == now.get_year())
            /* Translators: this is the month name and day number
             followed by a time string in 12h format.
             i.e. "May 25, 2:30 pm" */
            // xgettext:no-c-format
            format = N_("%B %-d, %l\u2236%M %p");
        else
            /* Translators: this is the month name, day number, year
             number followed by a time string in 12h format.
             i.e. "May 25 2012, 2:30 pm"*/
            // xgettext:no-c-format
            format = N_("%B %-d %Y, %l\u2236%M %p");
    }

    let formattedTime = date.format(Shell.util_translate_time_string(format));
    // prepend LTR-mark to colon/ratio to force a text direction on times
    return formattedTime.replace(/([:\u2236])/g, '\u200e$1');
}

function createTimeLabel(date, params) {
    if (_desktopSettings == null)
        _desktopSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });

    let label = new St.Label({ text: formatTime(date, params) });
    let id = _desktopSettings.connect('changed::clock-format', () => {
        label.text = formatTime(date, params);
    });
    label.connect('destroy', () => { _desktopSettings.disconnect(id); });
    return label;
}

// lowerBound:
// @array: an array or array-like object, already sorted
//         according to @cmp
// @val: the value to add
// @cmp: a comparator (or undefined to compare as numbers)
//
// Returns the position of the first element that is not
// lower than @val, according to @cmp.
// That is, returns the first position at which it
// is possible to insert @val without violating the
// order.
// This is quite like an ordinary binary search, except
// that it doesn't stop at first element comparing equal.

function lowerBound(array, val, cmp) {
    let min, max, mid, v;
    cmp = cmp || ((a, b) => a - b);

    if (array.length == 0)
        return 0;

    min = 0; max = array.length;
    while (min < (max - 1)) {
        mid = Math.floor((min + max) / 2);
        v = cmp(array[mid], val);

        if (v < 0)
            min = mid + 1;
        else
            max = mid;
    }

    return (min == max || cmp(array[min], val) < 0) ? max : min;
}

// insertSorted:
// @array: an array sorted according to @cmp
// @val: a value to insert
// @cmp: the sorting function
//
// Inserts @val into @array, preserving the
// sorting invariants.
// Returns the position at which it was inserted
function insertSorted(array, val, cmp) {
    let pos = lowerBound(array, val, cmp);
    array.splice(pos, 0, val);

    return pos;
}

var CloseButton = GObject.registerClass(
class CloseButton extends St.Button {
    _init(boxpointer) {
        super._init({ style_class: 'notification-close'});

        // This is a bit tricky. St.Bin has its own x-align/y-align properties
        // that compete with Clutter's properties. This should be fixed for
        // Clutter 2.0. Since St.Bin doesn't define its own setters, the
        // setters are a workaround to get Clutter's version.
        this.set_x_align(Clutter.ActorAlign.END);
        this.set_y_align(Clutter.ActorAlign.START);

        // XXX Clutter 2.0 workaround: ClutterBinLayout needs expand
        // to respect the alignments.
        this.set_x_expand(true);
        this.set_y_expand(true);

        this._boxPointer = boxpointer;
        if (boxpointer)
            this._boxPointer.connect('arrow-side-changed', this._sync.bind(this));
    }

    _computeBoxPointerOffset() {
        if (!this._boxPointer || !this._boxPointer.actor.get_stage())
            return 0;

        let side = this._boxPointer.arrowSide;
        if (side == St.Side.TOP)
            return this._boxPointer.getArrowHeight();
        else
            return 0;
    }

    _sync() {
        let themeNode = this.get_theme_node();

        let offY = this._computeBoxPointerOffset();
        this.translation_x = themeNode.get_length('-shell-close-overlap-x')
        this.translation_y = themeNode.get_length('-shell-close-overlap-y') + offY;
    }

    vfunc_style_changed() {
        this._sync();
        super.vfunc_style_changed();
    }
});

function makeCloseButton(boxpointer) {
    return new CloseButton(boxpointer);
}

function ensureActorVisibleInScrollView(scrollView, actor) {
    let adjustment = scrollView.vscroll.adjustment;
    let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();

    let offset = 0;
    let vfade = scrollView.get_effect("fade");
    if (vfade)
        offset = vfade.vfade_offset;

    let box = actor.get_allocation_box();
    let y1 = box.y1, y2 = box.y2;

    let parent = actor.get_parent();
    while (parent != scrollView) {
        if (!parent)
            throw new Error("actor not in scroll view");

        let box = parent.get_allocation_box();
        y1 += box.y1;
        y2 += box.y1;
        parent = parent.get_parent();
    }

    if (y1 < value + offset)
        value = Math.max(0, y1 - offset);
    else if (y2 > value + pageSize - offset)
        value = Math.min(upper, y2 + offset - pageSize);
    else
        return;

    Tweener.addTween(adjustment,
                     { value: value,
                       time: SCROLL_TIME,
                       transition: 'easeOutQuad' });
}

var AppSettingsMonitor = class {
    constructor(appId, schemaId) {
        this._appId = appId;
        this._schemaId = schemaId;

        this._app = null;
        this._settings = null;
        this._handlers = [];

        this._schemaSource = Gio.SettingsSchemaSource.get_default();

        this._appSystem = Shell.AppSystem.get_default();
        this._appSystem.connect('installed-changed',
                                this._onInstalledChanged.bind(this));
        this._onInstalledChanged();
    }

    get available() {
        return this._app != null && this._settings != null;
    }

    activateApp() {
        if (this._app)
            this._app.activate();
    }

    watchSetting(key, callback) {
        let handler = { id: 0, key: key, callback: callback };
        this._handlers.push(handler);

        this._connectHandler(handler);
    }

    _connectHandler(handler) {
        if (!this._settings || handler.id > 0)
            return;

        handler.id = this._settings.connect('changed::' + handler.key,
                                            handler.callback);
        handler.callback(this._settings, handler.key);
    }

    _disconnectHandler(handler) {
        if (this._settings && handler.id > 0)
            this._settings.disconnect(handler.id);
        handler.id = 0;
    }

    _onInstalledChanged() {
        let hadApp = (this._app != null);
        this._app = this._appSystem.lookup_app(this._appId);
        let haveApp = (this._app != null);

        if (hadApp == haveApp)
            return;

        if (haveApp)
            this._checkSettings();
        else
            this._setSettings(null);
    }

    _setSettings(settings) {
        this._handlers.forEach((handler) => { this._disconnectHandler(handler); });

        let hadSettings = (this._settings != null);
        this._settings = settings;
        let haveSettings = (this._settings != null);

        this._handlers.forEach((handler) => { this._connectHandler(handler); });

        if (hadSettings != haveSettings)
            this.emit('available-changed');
    }

    _checkSettings() {
        let schema = this._schemaSource.lookup(this._schemaId, true);
        if (schema) {
            this._setSettings(new Gio.Settings({ settings_schema: schema }));
        } else if (this._app) {
            Mainloop.timeout_add_seconds(1, () => {
                this._checkSettings();
                return GLib.SOURCE_REMOVE;
            });
        }
    }
};
Signals.addSignalMethods(AppSettingsMonitor.prototype);
(uuay)environment.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Config = imports.misc.config;

imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION;
imports.gi.versions.Gio = '2.0';
imports.gi.versions.GdkPixbuf = '2.0';
imports.gi.versions.Gtk = '3.0';
imports.gi.versions.TelepathyGLib = '0.12';
imports.gi.versions.TelepathyLogger = '0.2';

const { Clutter, Gio, GLib, Shell, St } = imports.gi;
const Gettext = imports.gettext;
const System = imports.system;

let _localTimeZone = null;

// We can't import shell JS modules yet, because they may have
// variable initializations, etc, that depend on init() already having
// been run.


// "monkey patch" in some varargs ClutterContainer methods; we need
// to do this per-container class since there is no representation
// of interfaces in Javascript
function _patchContainerClass(containerClass) {
    // This one is a straightforward mapping of the C method
    containerClass.prototype.child_set = function(actor, props) {
        let meta = this.get_child_meta(actor);
        for (let prop in props)
            meta[prop] = props[prop];
    };

    // clutter_container_add() actually is a an add-many-actors
    // method. We conveniently, but somewhat dubiously, take the
    // this opportunity to make it do something more useful.
    containerClass.prototype.add = function(actor, props) {
        this.add_actor(actor);
        if (props)
            this.child_set(actor, props);
    };
}

function _patchLayoutClass(layoutClass, styleProps) {
    if (styleProps)
        layoutClass.prototype.hookup_style = function(container) {
            container.connect('style-changed', () => {
                let node = container.get_theme_node();
                for (let prop in styleProps) {
                    let [found, length] = node.lookup_length(styleProps[prop], false);
                    if (found)
                        this[prop] = length;
                }
            });
        };
    layoutClass.prototype.child_set = function(actor, props) {
        let meta = this.get_child_meta(actor.get_parent(), actor);
        for (let prop in props)
            meta[prop] = props[prop];
    };
}

function _loggingFunc() {
    let fields = {'MESSAGE': [].join.call(arguments, ', ')};
    let domain = "GNOME Shell";

    // If the caller is an extension, add it as metadata
    let extension = imports.misc.extensionUtils.getCurrentExtension();
    if (extension != null) {
        domain = extension.metadata.name;
        fields['GNOME_SHELL_EXTENSION_UUID'] = extension.uuid;
        fields['GNOME_SHELL_EXTENSION_NAME'] = extension.metadata.name;
    }

    GLib.log_structured(domain, GLib.LogLevelFlags.LEVEL_MESSAGE, fields);
}

function init() {
    // Add some bindings to the global JS namespace; (gjs keeps the web
    // browser convention of having that namespace be called 'window'.)
    window.global = Shell.Global.get();

    window.log = _loggingFunc;

    window._ = Gettext.gettext;
    window.C_ = Gettext.pgettext;
    window.ngettext = Gettext.ngettext;
    window.N_ = s => s;

    // Miscellaneous monkeypatching
    _patchContainerClass(St.BoxLayout);

    _patchLayoutClass(Clutter.TableLayout, { row_spacing: 'spacing-rows',
                                             column_spacing: 'spacing-columns' });
    _patchLayoutClass(Clutter.GridLayout, { row_spacing: 'spacing-rows',
                                            column_spacing: 'spacing-columns' });
    _patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });

    Clutter.Actor.prototype.toString = function() {
        return St.describe_actor(this);
    };

    Gio._LocalFilePrototype.touch_async = function (callback) {
        Shell.util_touch_file_async(this, callback);
    };
    Gio._LocalFilePrototype.touch_finish = function (result) {
        return Shell.util_touch_file_finish(this, result);
    };

    let origToString = Object.prototype.toString;
    Object.prototype.toString = function() {
        let base = origToString.call(this);
        try {
            if ('actor' in this && this.actor instanceof Clutter.Actor)
                return base.replace(/\]$/, ' delegate for ' + this.actor.toString().substring(1));
            else
                return base;
        } catch(e) {
            return base;
        }
    };

    // Override to clear our own timezone cache as well
    const origClearDateCaches = System.clearDateCaches;
    System.clearDateCaches = function () {
        _localTimeZone = null;
        origClearDateCaches();
    };

    // Work around https://bugzilla.mozilla.org/show_bug.cgi?id=508783
    Date.prototype.toLocaleFormat = function(format) {
        if (_localTimeZone === null)
            _localTimeZone = GLib.TimeZone.new_local();

        let dt = GLib.DateTime.new(_localTimeZone,
            this.getFullYear(),
            this.getMonth() + 1,
            this.getDate(),
            this.getHours(),
            this.getMinutes(),
            this.getSeconds());
        return dt ? dt.format(format) : '';
    };

    let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
    if (slowdownEnv) {
        let factor = parseFloat(slowdownEnv);
        if (!isNaN(factor) && factor > 0.0)
            St.set_slow_down_factor(factor);
    }

    // OK, now things are initialized enough that we can import shell JS
    const Format = imports.format;
    const Tweener = imports.ui.tweener;

    Tweener.init();
    String.prototype.format = Format.format;
}
(uuay)audioDeviceSelection.js	const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;

const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;

const { loadInterfaceXML } = imports.misc.fileUtils;

var AudioDevice = {
    HEADPHONES: 1 << 0,
    HEADSET:    1 << 1,
    MICROPHONE: 1 << 2
};

const AudioDeviceSelectionIface = loadInterfaceXML('org.gnome.Shell.AudioDeviceSelection');

var AudioDeviceSelectionDialog =
class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog {
    constructor(devices) {
        super({ styleClass: 'audio-device-selection-dialog' });

        this._deviceItems = {};

        this._buildLayout();

        if (devices & AudioDevice.HEADPHONES)
            this._addDevice(AudioDevice.HEADPHONES);
        if (devices & AudioDevice.HEADSET)
            this._addDevice(AudioDevice.HEADSET);
        if (devices & AudioDevice.MICROPHONE)
            this._addDevice(AudioDevice.MICROPHONE);

        if (this._selectionBox.get_n_children() < 2)
            throw new Error('Too few devices for a selection');
    }

    destroy() {
        super.destroy();
    }

    _buildLayout(devices) {
        let title = new St.Label({ style_class: 'audio-selection-title',
                                   text: _("Select Audio Device"),
                                   x_align: Clutter.ActorAlign.CENTER });

        this.contentLayout.style_class = 'audio-selection-content';
        this.contentLayout.add(title);

        this._selectionBox = new St.BoxLayout({ style_class: 'audio-selection-box' });
        this.contentLayout.add(this._selectionBox, { expand: true });

        if (Main.sessionMode.allowSettings)
            this.addButton({ action: this._openSettings.bind(this),
                             label: _("Sound Settings") });
        this.addButton({ action: this.close.bind(this),
                         label: _("Cancel"),
                         key: Clutter.Escape });
    }

    _getDeviceLabel(device) {
        switch(device) {
            case AudioDevice.HEADPHONES:
                return _("Headphones");
            case AudioDevice.HEADSET:
                return _("Headset");
            case AudioDevice.MICROPHONE:
                return _("Microphone");
            default:
                return null;
        }
    }

    _getDeviceIcon(device) {
        switch(device) {
            case AudioDevice.HEADPHONES:
                return 'audio-headphones-symbolic';
            case AudioDevice.HEADSET:
                return 'audio-headset-symbolic';
            case AudioDevice.MICROPHONE:
                return 'audio-input-microphone-symbolic';
            default:
                return null;
        }
    }

    _addDevice(device) {
        let box = new St.BoxLayout({ style_class: 'audio-selection-device-box',
                                     vertical: true });
        box.connect('notify::height', () => {
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                box.width = box.height;
            });
        });

        let icon = new St.Icon({ style_class: 'audio-selection-device-icon',
                                 icon_name: this._getDeviceIcon(device) });
        box.add(icon);

        let label = new St.Label({ style_class: 'audio-selection-device-label',
                                   text: this._getDeviceLabel(device),
                                   x_align: Clutter.ActorAlign.CENTER });
        box.add(label);

        let button = new St.Button({ style_class: 'audio-selection-device',
                                     can_focus: true,
                                     child: box });
        this._selectionBox.add(button);

        button.connect('clicked', () => {
            this.emit('device-selected', device);
            this.close();
            Main.overview.hide();
        });
    }

    _openSettings() {
        let desktopFile = 'gnome-sound-panel.desktop'
        let app = Shell.AppSystem.get_default().lookup_app(desktopFile);

        if (!app) {
            log('Settings panel for desktop file ' + desktopFile + ' could not be loaded!');
            return;
        }

        this.close();
        Main.overview.hide();
        app.activate();
    }
};

var AudioDeviceSelectionDBus = class AudioDeviceSelectionDBus {
    constructor() {
        this._audioSelectionDialog = null;

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(AudioDeviceSelectionIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/AudioDeviceSelection');

        Gio.DBus.session.own_name('org.gnome.Shell.AudioDeviceSelection', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    _onDialogClosed() {
        this._audioSelectionDialog = null;
    }

    _onDeviceSelected(dialog, device) {
        let connection = this._dbusImpl.get_connection();
        let info = this._dbusImpl.get_info();
        let deviceName = Object.keys(AudioDevice).filter(
            dev => AudioDevice[dev] == device
        )[0].toLowerCase();
        connection.emit_signal(this._audioSelectionDialog._sender,
                               this._dbusImpl.get_object_path(),
                               info ? info.name : null,
                               'DeviceSelected',
                               GLib.Variant.new('(s)', [deviceName]));
    }

    OpenAsync(params, invocation) {
        if (this._audioSelectionDialog) {
            invocation.return_value(null);
            return;
        }

        let [deviceNames] = params;
        let devices = 0;
        deviceNames.forEach(n => { devices |= AudioDevice[n.toUpperCase()]; });

        let dialog;
        try {
            dialog = new AudioDeviceSelectionDialog(devices);
        } catch(e) {
            invocation.return_value(null);
            return;
        }
        dialog._sender = invocation.get_sender();

        dialog.connect('closed', this._onDialogClosed.bind(this));
        dialog.connect('device-selected',
                       this._onDeviceSelected.bind(this));
        dialog.open();

        this._audioSelectionDialog = dialog;
        invocation.return_value(null);
    }

    CloseAsync(params, invocation) {
        if (this._audioSelectionDialog &&
            this._audioSelectionDialog._sender == invocation.get_sender())
            this._audioSelectionDialog.close();

        invocation.return_value(null);
    }
};
(uuay)osdMonitorLabeler.js
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, Meta, St } = imports.gi;

const Main = imports.ui.main;

var FADE_TIME = 0.1;

var OsdMonitorLabel = class {
    constructor(monitor, label) {
        this._actor = new St.Widget({ x_expand: true,
                                      y_expand: true });

        this._monitor = monitor;

        this._box = new St.BoxLayout({ style_class: 'osd-window',
                                       vertical: true });
        this._actor.add_actor(this._box);

        this._label = new St.Label({ style_class: 'osd-monitor-label',
                                     text: label });
        this._box.add(this._label);

        Main.uiGroup.add_child(this._actor);
        Main.uiGroup.set_child_above_sibling(this._actor, null);
        this._position();

        Meta.disable_unredirect_for_display(global.display);
    }

    _position() {
        let workArea = Main.layoutManager.getWorkAreaForMonitor(this._monitor);

        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
            this._box.x = workArea.x + (workArea.width - this._box.width);
        else
            this._box.x = workArea.x;

        this._box.y = workArea.y;
    }

    destroy() {
        this._actor.destroy();
        Meta.enable_unredirect_for_display(global.display);
    }
};

var OsdMonitorLabeler = class {
    constructor() {
        this._monitorManager = Meta.MonitorManager.get();
        this._client = null;
        this._clientWatchId = 0;
        this._osdLabels = [];
        this._monitorLabels = null;
        Main.layoutManager.connect('monitors-changed',
                                    this._reset.bind(this));
        this._reset();
    }

    _reset() {
        for (let i in this._osdLabels)
            this._osdLabels[i].destroy();
        this._osdLabels = [];
        this._monitorLabels = new Map();
        let monitors = Main.layoutManager.monitors;
        for (let i in monitors)
            this._monitorLabels.set(monitors[i].index, []);
    }

    _trackClient(client) {
        if (this._client)
            return (this._client == client);

        this._client = client;
        this._clientWatchId = Gio.bus_watch_name(Gio.BusType.SESSION, client, 0, null,
                                                 (c, name) => {
                                                     this.hide(name);
                                                 });
        return true;
    }

    _untrackClient(client) {
        if (!this._client || this._client != client)
            return false;

        Gio.bus_unwatch_name(this._clientWatchId);
        this._clientWatchId = 0;
        this._client = null;
        return true;
    }

    show(client, params) {
        if (!this._trackClient(client))
            return;

        this._reset();

        for (let connector in params) {
            let monitor = this._monitorManager.get_monitor_for_connector(connector);
            if (monitor == -1)
                continue;
            this._monitorLabels.get(monitor).push(params[connector].deep_unpack());
        }

        for (let [monitor, labels] of this._monitorLabels.entries()) {
            labels.sort();
            this._osdLabels.push(new OsdMonitorLabel(monitor, labels.join(' ')));
        }
    }

    hide(client) {
        if (!this._untrackClient(client))
            return;

        this._reset();
    }
};
(uuay)endSessionDialog.js�q// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
 * Copyright 2010-2016 Red Hat, Inc
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

const Mainloop = imports.mainloop;

const { AccountsService, Clutter, Gio,
        GLib, Pango, Polkit, Shell, St }  = imports.gi;

const CheckBox = imports.ui.checkBox;
const GnomeSession = imports.misc.gnomeSession;
const LoginManager = imports.misc.loginManager;
const ModalDialog = imports.ui.modalDialog;
const UserWidget = imports.ui.userWidget;

const { loadInterfaceXML } = imports.misc.fileUtils;

let _endSessionDialog = null;

const _ITEM_ICON_SIZE = 48;
const _DIALOG_ICON_SIZE = 48;

var GSM_SESSION_MANAGER_LOGOUT_FORCE = 2;

const EndSessionDialogIface = loadInterfaceXML('org.gnome.SessionManager.EndSessionDialog');

const logoutDialogContent = {
    subjectWithUser: C_("title", "Log Out %s"),
    subject: C_("title", "Log Out"),
    descriptionWithUser(user, seconds) {
        return ngettext("%s will be logged out automatically in %d second.",
                        "%s will be logged out automatically in %d seconds.",
                        seconds).format(user, seconds);
    },
    description(seconds) {
        return ngettext("You will be logged out automatically in %d second.",
                        "You will be logged out automatically in %d seconds.",
                        seconds).format(seconds);
    },
    showBatteryWarning: false,
    confirmButtons: [{ signal: 'ConfirmedLogout',
                       label:  C_("button", "Log Out") }],
    iconStyleClass: 'end-session-dialog-logout-icon',
    showOtherSessions: false,
};

const shutdownDialogContent = {
    subject: C_("title", "Power Off"),
    subjectWithUpdates: C_("title", "Install Updates & Power Off"),
    description(seconds) {
        return ngettext("The system will power off automatically in %d second.",
                        "The system will power off automatically in %d seconds.",
                        seconds).format(seconds);
    },
    checkBoxText: C_("checkbox", "Install pending software updates"),
    showBatteryWarning: true,
    confirmButtons: [{ signal: 'ConfirmedReboot',
                       label:  C_("button", "Restart") },
                     { signal: 'ConfirmedShutdown',
                       label:  C_("button", "Power Off") }],
    iconName: 'system-shutdown-symbolic',
    iconStyleClass: 'end-session-dialog-shutdown-icon',
    showOtherSessions: true,
};

const restartDialogContent = {
    subject: C_("title", "Restart"),
    description(seconds) {
        return ngettext("The system will restart automatically in %d second.",
                        "The system will restart automatically in %d seconds.",
                        seconds).format(seconds);
    },
    showBatteryWarning: false,
    confirmButtons: [{ signal: 'ConfirmedReboot',
                       label:  C_("button", "Restart") }],
    iconName: 'view-refresh-symbolic',
    iconStyleClass: 'end-session-dialog-shutdown-icon',
    showOtherSessions: true,
};

const restartUpdateDialogContent = {

    subject: C_("title", "Restart & Install Updates"),
    description(seconds) {
        return ngettext("The system will automatically restart and install updates in %d second.",
                        "The system will automatically restart and install updates in %d seconds.",
                        seconds).format(seconds);
    },
    showBatteryWarning: true,
    confirmButtons: [{ signal: 'ConfirmedReboot',
                       label:  C_("button", "Restart &amp; Install") }],
    unusedFutureButtonForTranslation: C_("button", "Install &amp; Power Off"),
    unusedFutureCheckBoxForTranslation: C_("checkbox", "Power off after updates are installed"),
    iconName: 'view-refresh-symbolic',
    iconStyleClass: 'end-session-dialog-shutdown-icon',
    showOtherSessions: true,
};

const restartUpgradeDialogContent = {

    subject: C_("title", "Restart & Install Upgrade"),
    upgradeDescription(distroName, distroVersion) {
        /* Translators: This is the text displayed for system upgrades in the
           shut down dialog. First %s gets replaced with the distro name and
           second %s with the distro version to upgrade to */
        return _("%s %s will be installed after restart. Upgrade installation can take a long time: ensure that you have backed up and that the computer is plugged in.").format(distroName, distroVersion);
    },
    disableTimer: true,
    showBatteryWarning: false,
    confirmButtons: [{ signal: 'ConfirmedReboot',
                       label:  C_("button", "Restart &amp; Install") }],
    iconName: 'view-refresh-symbolic',
    iconStyleClass: 'end-session-dialog-shutdown-icon',
    showOtherSessions: true,
};

const DialogType = {
  LOGOUT: 0 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_LOGOUT */,
  SHUTDOWN: 1 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_SHUTDOWN */,
  RESTART: 2 /* GSM_SHELL_END_SESSION_DIALOG_TYPE_RESTART */,
  UPDATE_RESTART: 3,
  UPGRADE_RESTART: 4
};

const DialogContent = {
    0 /* DialogType.LOGOUT */: logoutDialogContent,
    1 /* DialogType.SHUTDOWN */: shutdownDialogContent,
    2 /* DialogType.RESTART */: restartDialogContent,
    3 /* DialogType.UPDATE_RESTART */: restartUpdateDialogContent,
    4 /* DialogType.UPGRADE_RESTART */: restartUpgradeDialogContent
};

var MAX_USERS_IN_SESSION_DIALOG = 5;

const LogindSessionIface = loadInterfaceXML('org.freedesktop.login1.Session');
const LogindSession = Gio.DBusProxy.makeProxyWrapper(LogindSessionIface);

const PkOfflineIface = loadInterfaceXML('org.freedesktop.PackageKit.Offline');
const PkOfflineProxy = Gio.DBusProxy.makeProxyWrapper(PkOfflineIface);

const UPowerIface = loadInterfaceXML('org.freedesktop.UPower');
const UPowerProxy = Gio.DBusProxy.makeProxyWrapper(UPowerIface);

function findAppFromInhibitor(inhibitor) {
    let desktopFile;
    try {
        [desktopFile] = inhibitor.GetAppIdSync();
    } catch(e) {
        // XXX -- sometimes JIT inhibitors generated by gnome-session
        // get removed too soon. Don't fail in this case.
        log('gnome-session gave us a dead inhibitor: %s'.format(inhibitor.get_object_path()));
        return null;
    }

    if (!GLib.str_has_suffix(desktopFile, '.desktop'))
        desktopFile += '.desktop';

    return Shell.AppSystem.get_default().lookup_heuristic_basename(desktopFile);
}

// The logout timer only shows updates every 10 seconds
// until the last 10 seconds, then it shows updates every
// second.  This function takes a given time and returns
// what we should show to the user for that time.
function _roundSecondsToInterval(totalSeconds, secondsLeft, interval) {
    let time;

    time = Math.ceil(secondsLeft);

    // Final count down is in decrements of 1
    if (time <= interval)
        return time;

    // Round up higher than last displayable time interval
    time += interval - 1;

    // Then round down to that time interval
    if (time > totalSeconds)
        time = Math.ceil(totalSeconds);
    else
        time -= time % interval;

    return time;
}

function _setLabelText(label, text) {
    if (text) {
        label.set_text(text);
        label.show();
    } else {
        label.set_text('');
        label.hide();
    }
}

function _setCheckBoxLabel(checkBox, text) {
    let label = checkBox.getLabelActor();

    if (text) {
        label.set_text(text);
        checkBox.actor.show();
    } else {
        label.set_text('');
        checkBox.actor.hide();
    }
}

function init() {
    // This always returns the same singleton object
    // By instantiating it initially, we register the
    // bus object, etc.
    _endSessionDialog = new EndSessionDialog();
}

var EndSessionDialog = class EndSessionDialog extends ModalDialog.ModalDialog {
    constructor() {
        super({ styleClass: 'end-session-dialog',
                destroyOnClose: false });

        this._loginManager = LoginManager.getLoginManager();
        this._userManager = AccountsService.UserManager.get_default();
        this._user = this._userManager.get_user(GLib.get_user_name());

        this._pkOfflineProxy = new PkOfflineProxy(Gio.DBus.system,
                                                  'org.freedesktop.PackageKit',
                                                  '/org/freedesktop/PackageKit',
                                                  (proxy, error) => {
                                                      if (error)
                                                          log(error.message);
                                                  });
        this._powerProxy = new UPowerProxy(Gio.DBus.system,
                                           'org.freedesktop.UPower',
                                           '/org/freedesktop/UPower',
                                           (proxy, error) => {
                                               if (error) {
                                                   log(error.message);
                                                   return;
                                               }
                                               this._powerProxy.connect('g-properties-changed',
                                                                        this._sync.bind(this));
                                               this._sync();
                                           });

        this._secondsLeft = 0;
        this._totalSecondsToStayOpen = 0;
        this._applications = [];
        this._sessions = [];

        this.connect('destroy',
                     this._onDestroy.bind(this));
        this.connect('opened',
                     this._onOpened.bind(this));

        this._userLoadedId = this._user.connect('notify::is_loaded', this._sync.bind(this));
        this._userChangedId = this._user.connect('changed', this._sync.bind(this));

        let mainContentLayout = new St.BoxLayout({ vertical: false });
        this.contentLayout.add(mainContentLayout,
                               { x_fill: true,
                                 y_fill: false });

        this._iconBin = new St.Bin();
        mainContentLayout.add(this._iconBin,
                              { x_fill:  true,
                                y_fill:  false,
                                x_align: St.Align.END,
                                y_align: St.Align.START });

        let messageLayout = new St.BoxLayout({ vertical: true,
                                               style_class: 'end-session-dialog-layout' });
        mainContentLayout.add(messageLayout,
                              { y_align: St.Align.START });

        this._subjectLabel = new St.Label({ style_class: 'end-session-dialog-subject' });

        messageLayout.add(this._subjectLabel,
                          { x_fill: false,
                            y_fill:  false,
                            x_align: St.Align.START,
                            y_align: St.Align.START });

        this._descriptionLabel = new St.Label({ style_class: 'end-session-dialog-description' });
        this._descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._descriptionLabel.clutter_text.line_wrap = true;

        messageLayout.add(this._descriptionLabel,
                          { y_fill:  true,
                            y_align: St.Align.START });

        this._checkBox = new CheckBox.CheckBox();
        this._checkBox.actor.connect('clicked', this._sync.bind(this));
        messageLayout.add(this._checkBox.actor);

        this._batteryWarning = new St.Label({ style_class: 'end-session-dialog-warning',
                                              text: _("Running on battery power: please plug in before installing updates.") });
        this._batteryWarning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._batteryWarning.clutter_text.line_wrap = true;
        messageLayout.add(this._batteryWarning);

        this._scrollView = new St.ScrollView({ style_class: 'end-session-dialog-list' });
        this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        this.contentLayout.add(this._scrollView,
                               { x_fill: true,
                                 y_fill: true });
        this._scrollView.hide();

        this._inhibitorSection = new St.BoxLayout({ vertical: true,
                                                    style_class: 'end-session-dialog-inhibitor-layout' });
        this._scrollView.add_actor(this._inhibitorSection);

        this._applicationHeader = new St.Label({ style_class: 'end-session-dialog-list-header',
                                                 text: _("Some applications are busy or have unsaved work.") });
        this._applicationList = new St.BoxLayout({ style_class: 'end-session-dialog-app-list',
                                                   vertical: true });
        this._inhibitorSection.add_actor(this._applicationHeader);
        this._inhibitorSection.add_actor(this._applicationList);

        this._sessionHeader = new St.Label({ style_class: 'end-session-dialog-list-header',
                                             text: _("Other users are logged in.") });
        this._sessionList = new St.BoxLayout({ style_class: 'end-session-dialog-session-list',
                                               vertical: true });
        this._inhibitorSection.add_actor(this._sessionHeader);
        this._inhibitorSection.add_actor(this._sessionList);

        try {
            this._updatesPermission = Polkit.Permission.new_sync("org.freedesktop.packagekit.trigger-offline-update", null, null);
        } catch(e) {
            log('No permission to trigger offline updates: %s'.format(e.toString()));
        }

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog');
    }

    _onDestroy() {
        this._user.disconnect(this._userLoadedId);
        this._user.disconnect(this._userChangedId);
    }

    _sync() {
        let open = (this.state == ModalDialog.State.OPENING || this.state == ModalDialog.State.OPENED);
        if (!open)
            return;

        let dialogContent = DialogContent[this._type];

        let subject = dialogContent.subject;

        // Use different title when we are installing updates
        if (dialogContent.subjectWithUpdates && this._checkBox.actor.checked)
            subject = dialogContent.subjectWithUpdates;

        if (dialogContent.showBatteryWarning) {
            // Warn when running on battery power
            if (this._powerProxy.OnBattery && this._checkBox.actor.checked)
                this._batteryWarning.opacity = 255;
            else
                this._batteryWarning.opacity = 0;
        }

        let description;
        let displayTime = _roundSecondsToInterval(this._totalSecondsToStayOpen,
                                                  this._secondsLeft,
                                                  10);

        if (this._user.is_loaded) {
            let realName = this._user.get_real_name();

            if (realName != null) {
                if (dialogContent.subjectWithUser)
                    subject = dialogContent.subjectWithUser.format(realName);

                if (dialogContent.descriptionWithUser)
                    description = dialogContent.descriptionWithUser(realName, displayTime);
            }
        }

        // Use a different description when we are installing a system upgrade
        if (dialogContent.upgradeDescription) {
            let name = this._pkOfflineProxy.PreparedUpgrade['name'].deep_unpack();
            let version = this._pkOfflineProxy.PreparedUpgrade['version'].deep_unpack();

            if (name != null && version != null)
                description = dialogContent.upgradeDescription(name, version);
        }

        // Fall back to regular description
        if (!description)
            description = dialogContent.description(displayTime);

        _setLabelText(this._descriptionLabel, description);
        _setLabelText(this._subjectLabel, subject);

        if (dialogContent.iconName) {
            this._iconBin.child = new St.Icon({ icon_name: dialogContent.iconName,
                                                icon_size: _DIALOG_ICON_SIZE,
                                                style_class: dialogContent.iconStyleClass });
        } else {
            let avatarWidget = new UserWidget.Avatar(this._user,
                                                     { iconSize: _DIALOG_ICON_SIZE,
                                                       styleClass: dialogContent.iconStyleClass });
            this._iconBin.child = avatarWidget.actor;
            avatarWidget.update();
        }

        let hasApplications = this._applications.length > 0;
        let hasSessions = this._sessions.length > 0;
        this._scrollView.visible = hasApplications || hasSessions;
        this._applicationHeader.visible = hasApplications;
        this._sessionHeader.visible = hasSessions;
    }

    _updateButtons() {
        let dialogContent = DialogContent[this._type];
        let buttons = [{ action: this.cancel.bind(this),
                         label:  _("Cancel"),
                         key:    Clutter.Escape }];

        for (let i = 0; i < dialogContent.confirmButtons.length; i++) {
            let signal = dialogContent.confirmButtons[i].signal;
            let label = dialogContent.confirmButtons[i].label;
            buttons.push({ action: () => {
                               this.close(true);
                               let signalId = this.connect('closed', () => {
                                   this.disconnect(signalId);
                                   this._confirm(signal);
                               });
                           },
                           label: label });
        }

        this.setButtons(buttons);
    }

    close(skipSignal) {
        super.close();

        if (!skipSignal)
            this._dbusImpl.emit_signal('Closed', null);
    }

    cancel() {
        this._stopTimer();
        this._dbusImpl.emit_signal('Canceled', null);
        this.close();
    }

    _confirm(signal) {
        let callback = () => {
            this._fadeOutDialog();
            this._stopTimer();
            this._dbusImpl.emit_signal(signal, null);
        };

        // Offline update not available; just emit the signal
        if (!this._checkBox.actor.visible) {
            callback();
            return;
        }

        // Trigger the offline update as requested
        if (this._checkBox.actor.checked) {
            switch (signal) {
                case "ConfirmedReboot":
                    this._triggerOfflineUpdateReboot(callback);
                    break;
                case "ConfirmedShutdown":
                    // To actually trigger the offline update, we need to
                    // reboot to do the upgrade. When the upgrade is complete,
                    // the computer will shut down automatically.
                    signal = "ConfirmedReboot";
                    this._triggerOfflineUpdateShutdown(callback);
                    break;
                default:
                    callback();
                    break;
            }
        } else {
            this._triggerOfflineUpdateCancel(callback);
        }
    }

    _onOpened() {
        this._sync();
    }

    _triggerOfflineUpdateReboot(callback) {
        this._pkOfflineProxy.TriggerRemote('reboot', (result, error) => {
            if (error)
                log(error.message);

            callback();
        });
    }

    _triggerOfflineUpdateShutdown(callback) {
        this._pkOfflineProxy.TriggerRemote('power-off', (result, error) => {
            if (error)
                log(error.message);

            callback();
        });
    }

    _triggerOfflineUpdateCancel(callback) {
        this._pkOfflineProxy.CancelRemote((result, error) => {
            if (error)
                log(error.message);

            callback();
        });
    }

    _startTimer() {
        let startTime = GLib.get_monotonic_time();
        this._secondsLeft = this._totalSecondsToStayOpen;

        this._timerId = Mainloop.timeout_add_seconds(1, () => {
            let currentTime = GLib.get_monotonic_time();
            let secondsElapsed = ((currentTime - startTime) / 1000000);

            this._secondsLeft = this._totalSecondsToStayOpen - secondsElapsed;
            if (this._secondsLeft > 0) {
                this._sync();
                return GLib.SOURCE_CONTINUE;
            }

            let dialogContent = DialogContent[this._type];
            let button = dialogContent.confirmButtons[dialogContent.confirmButtons.length - 1];
            this._confirm(button.signal);
            this._timerId = 0;

            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(this._timerId, '[gnome-shell] this._confirm');
    }

    _stopTimer() {
        if (this._timerId > 0) {
            Mainloop.source_remove(this._timerId);
            this._timerId = 0;
        }

        this._secondsLeft = 0;
    }

    _constructListItemForApp(inhibitor, app) {
        let actor = new St.BoxLayout({ style_class: 'end-session-dialog-app-list-item',
                                       can_focus: true });
        actor.add(app.create_icon_texture(_ITEM_ICON_SIZE));

        let textLayout = new St.BoxLayout({ vertical: true,
                                            y_expand: true,
                                            y_align: Clutter.ActorAlign.CENTER });
        actor.add(textLayout);

        let nameLabel = new St.Label({ text: app.get_name(),
                                       style_class: 'end-session-dialog-app-list-item-name' });
        textLayout.add(nameLabel);
        actor.label_actor = nameLabel;

        let [reason] = inhibitor.GetReasonSync();
        if (reason) {
            let reasonLabel = new St.Label({ text: reason,
                                             style_class: 'end-session-dialog-app-list-item-description' });
            textLayout.add(reasonLabel);
        }

        return actor;
    }

    _onInhibitorLoaded(inhibitor) {
        if (this._applications.indexOf(inhibitor) < 0) {
            // Stale inhibitor
            return;
        }

        let app = findAppFromInhibitor(inhibitor);

        if (app) {
            let actor = this._constructListItemForApp(inhibitor, app);
            this._applicationList.add(actor);
        } else {
            // inhibiting app is a service, not an application
            this._applications.splice(this._applications.indexOf(inhibitor), 1);
        }

        this._sync();
    }

    _constructListItemForSession(session) {
        let avatar = new UserWidget.Avatar(session.user, { iconSize: _ITEM_ICON_SIZE });
        avatar.update();

        let userName = session.user.get_real_name() ? session.user.get_real_name() : session.username;
        let userLabelText;

        if (session.remote)
            /* Translators: Remote here refers to a remote session, like a ssh login */
            userLabelText = _("%s (remote)").format(userName);
        else if (session.type == "tty")
            /* Translators: Console here refers to a tty like a VT console */
            userLabelText = _("%s (console)").format(userName);
        else
            userLabelText = userName;

        let actor = new St.BoxLayout({ style_class: 'end-session-dialog-session-list-item',
                                       can_focus: true });
        actor.add(avatar.actor);

        let nameLabel = new St.Label({ text: userLabelText,
                                       style_class: 'end-session-dialog-session-list-item-name',
                                       y_expand: true,
                                       y_align: Clutter.ActorAlign.CENTER });
        actor.add(nameLabel);
        actor.label_actor = nameLabel;

        return actor;
    }

    _loadSessions() {
        this._loginManager.listSessions(result => {
            let n = 0;
            for (let i = 0; i < result.length; i++) {
                let[id, uid, userName, seat, sessionPath] = result[i];
                let proxy = new LogindSession(Gio.DBus.system, 'org.freedesktop.login1', sessionPath);

                if (proxy.Class != 'user')
                    continue;

                if (proxy.State == 'closing')
                    continue;

                let sessionId = GLib.getenv('XDG_SESSION_ID');
                if (!sessionId)
                    this._loginManager.getCurrentSessionProxy(currentSessionProxy => {
                        sessionId = currentSessionProxy.Id;
                        log(`endSessionDialog: No XDG_SESSION_ID, fetched from logind: ${sessionId}`);
                    });

                if (proxy.Id == sessionId)
                    continue;

                let session = { user: this._userManager.get_user(userName),
                                username: userName,
                                type: proxy.Type,
                                remote: proxy.Remote };
                this._sessions.push(session);

                let actor = this._constructListItemForSession(session);
                this._sessionList.add(actor);

                // limit the number of entries
                n++;
                if (n == MAX_USERS_IN_SESSION_DIALOG)
                    break;
            }

            this._sync();
        });
    }

    OpenAsync(parameters, invocation) {
        let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters;
        this._totalSecondsToStayOpen = totalSecondsToStayOpen;
        this._type = type;

        if (this._type == DialogType.RESTART) {
            if (this._pkOfflineProxy.UpdateTriggered)
                this._type = DialogType.UPDATE_RESTART;
            else if (this._pkOfflineProxy.UpgradeTriggered)
                this._type = DialogType.UPGRADE_RESTART;
        }

        this._applications = [];
        this._applicationList.destroy_all_children();

        this._sessions = [];
        this._sessionList.destroy_all_children();

        if (!(this._type in DialogContent)) {
            invocation.return_dbus_error('org.gnome.Shell.ModalDialog.TypeError',
                                         "Unknown dialog type requested");
            return;
        }

        let dialogContent = DialogContent[this._type];

        for (let i = 0; i < inhibitorObjectPaths.length; i++) {
            let inhibitor = new GnomeSession.Inhibitor(inhibitorObjectPaths[i], (proxy, error) => {
                this._onInhibitorLoaded(proxy);
            });

            this._applications.push(inhibitor);
        }

        if (dialogContent.showOtherSessions)
            this._loadSessions();

        let updateTriggered = this._pkOfflineProxy.UpdateTriggered;
        let updatePrepared = this._pkOfflineProxy.UpdatePrepared;
        let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;

        _setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText || '');
        this._checkBox.actor.visible = (dialogContent.checkBoxText && updatePrepared && updatesAllowed);
        this._checkBox.actor.checked = (updatePrepared && updateTriggered);

        // We show the warning either together with the checkbox, or when
        // updates have already been triggered, but the user doesn't have
        // enough permissions to cancel them.
        this._batteryWarning.visible = (dialogContent.showBatteryWarning &&
                                        (this._checkBox.actor.visible || updatePrepared && updateTriggered && !updatesAllowed));

        this._updateButtons();

        if (!this.open(timestamp)) {
            invocation.return_dbus_error('org.gnome.Shell.ModalDialog.GrabError',
                                         "Cannot grab pointer and keyboard");
            return;
        }

        if (!dialogContent.disableTimer)
            this._startTimer();

        this._sync();

        let signalId = this.connect('opened', () => {
            invocation.return_value(null);
            this.disconnect(signalId);
        });
    }

    Close(parameters, invocation) {
        this.close();
    }
};
(uuay)panelMenu.js'// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, GObject, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const Params = imports.misc.params;
const PopupMenu = imports.ui.popupMenu;

var ButtonBox = GObject.registerClass(
class ButtonBox extends St.Widget {
    _init(params) {
        params = Params.parse(params, { style_class: 'panel-button' }, true);

        super._init(params);

        this.actor = this;
        this._delegate = this;

        this.container = new St.Bin({ y_fill: true,
                                      x_fill: true,
                                      child: this.actor });

        this.connect('style-changed', this._onStyleChanged.bind(this));
        this.connect('destroy', this._onDestroy.bind(this));

        this._minHPadding = this._natHPadding = 0.0;
    }

    _onStyleChanged(actor) {
        let themeNode = actor.get_theme_node();

        this._minHPadding = themeNode.get_length('-minimum-hpadding');
        this._natHPadding = themeNode.get_length('-natural-hpadding');
    }

    vfunc_get_preferred_width(forHeight) {
        let child = this.get_first_child();
        let minimumSize, naturalSize;

        if (child)
            [minimumSize, naturalSize] = child.get_preferred_width(-1);
        else
            minimumSize = naturalSize = 0;

        minimumSize += 2 * this._minHPadding;
        naturalSize += 2 * this._natHPadding;

        return [minimumSize, naturalSize];
    }

    vfunc_get_preferred_height(forWidth) {
        let child = this.get_first_child();

        if (child)
            return child.get_preferred_height(-1);

        return [0, 0];
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let child = this.get_first_child();
        if (!child)
            return;

        let [minWidth, natWidth] = child.get_preferred_width(-1);

        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;

        let childBox = new Clutter.ActorBox();
        if (natWidth + 2 * this._natHPadding <= availWidth) {
            childBox.x1 = this._natHPadding;
            childBox.x2 = availWidth - this._natHPadding;
        } else {
            childBox.x1 = this._minHPadding;
            childBox.x2 = availWidth - this._minHPadding;
        }

        childBox.y1 = 0;
        childBox.y2 = availHeight;

        child.allocate(childBox, flags);
    }

    _onDestroy() {
        this.container.child = null;
        this.container.destroy();
    }
});

var Button = GObject.registerClass({
    Signals: {'menu-set': {} },
}, class PanelMenuButton extends ButtonBox {
    _init(menuAlignment, nameText, dontCreateMenu) {
        super._init({ reactive: true,
                      can_focus: true,
                      track_hover: true,
                      accessible_name: nameText ? nameText : "",
                      accessible_role: Atk.Role.MENU });

        this.connect('event', this._onEvent.bind(this));
        this.connect('notify::visible', this._onVisibilityChanged.bind(this));

        if (dontCreateMenu)
            this.menu = new PopupMenu.PopupDummyMenu(this.actor);
        else
            this.setMenu(new PopupMenu.PopupMenu(this.actor, menuAlignment, St.Side.TOP, 0));
    }

    setSensitive(sensitive) {
        this.reactive = sensitive;
        this.can_focus = sensitive;
        this.track_hover = sensitive;
    }

    setMenu(menu) {
        if (this.menu)
            this.menu.destroy();

        this.menu = menu;
        if (this.menu) {
            this.menu.actor.add_style_class_name('panel-menu');
            this.menu.connect('open-state-changed', this._onOpenStateChanged.bind(this));
            this.menu.actor.connect('key-press-event', this._onMenuKeyPress.bind(this));

            Main.uiGroup.add_actor(this.menu.actor);
            this.menu.actor.hide();
        }
        this.emit('menu-set');
    }

    _onEvent(actor, event) {
        if (this.menu &&
            (event.type() == Clutter.EventType.TOUCH_BEGIN ||
             event.type() == Clutter.EventType.BUTTON_PRESS))
            this.menu.toggle();

        return Clutter.EVENT_PROPAGATE;
    }

    _onVisibilityChanged() {
        if (!this.menu)
            return;

        if (!this.actor.visible)
            this.menu.close();
    }

    _onMenuKeyPress(actor, event) {
        if (global.focus_manager.navigate_from_event(event))
            return Clutter.EVENT_STOP;

        let symbol = event.get_key_symbol();
        if (symbol == Clutter.KEY_Left || symbol == Clutter.KEY_Right) {
            let group = global.focus_manager.get_group(this.actor);
            if (group) {
                let direction = (symbol == Clutter.KEY_Left) ? St.DirectionType.LEFT : St.DirectionType.RIGHT;
                group.navigate_focus(this.actor, direction, false);
                return Clutter.EVENT_STOP;
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onOpenStateChanged(menu, open) {
        if (open)
            this.actor.add_style_pseudo_class('active');
        else
            this.actor.remove_style_pseudo_class('active');

        // Setting the max-height won't do any good if the minimum height of the
        // menu is higher then the screen; it's useful if part of the menu is
        // scrollable so the minimum height is smaller than the natural height
        let workArea = Main.layoutManager.getWorkAreaForMonitor(Main.layoutManager.primaryIndex);
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        let verticalMargins = this.menu.actor.margin_top + this.menu.actor.margin_bottom;

        // The workarea and margin dimensions are in physical pixels, but CSS
        // measures are in logical pixels, so make sure to consider the scale
        // factor when computing max-height
        let maxHeight = Math.round((workArea.height - verticalMargins) / scaleFactor);
        this.menu.actor.style = ('max-height: %spx;').format(maxHeight);
    }

    _onDestroy() {
        super._onDestroy();

        if (this.menu)
            this.menu.destroy();
    }
});

/* SystemIndicator:
 *
 * This class manages one system indicator, which are the icons
 * that you see at the top right. A system indicator is composed
 * of an icon and a menu section, which will be composed into the
 * aggregate menu.
 */
var SystemIndicator = class {
    constructor() {
        this.indicators = new St.BoxLayout({ style_class: 'panel-status-indicators-box',
                                             reactive: true });
        this.indicators.hide();
        this.menu = new PopupMenu.PopupMenuSection();
    }

    _syncIndicatorsVisible() {
        this.indicators.visible = this.indicators.get_children().some(a => a.visible);
    }

    _addIndicator() {
        let icon = new St.Icon({ style_class: 'system-status-icon' });
        this.indicators.add_actor(icon);
        icon.connect('notify::visible', this._syncIndicatorsVisible.bind(this));
        this._syncIndicatorsVisible();
        return icon;
    }
};
Signals.addSignalMethods(SystemIndicator.prototype);
(uuay)system.jsJ4// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { AccountsService, Clutter, GLib, GObject, Shell, St } = imports.gi;

const BoxPointer = imports.ui.boxpointer;
const SystemActions = imports.misc.systemActions;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;


var AltSwitcher = class {
    constructor(standard, alternate) {
        this._standard = standard;
        this._standard.connect('notify::visible', this._sync.bind(this));
        if (this._standard instanceof St.Button)
            this._standard.connect('clicked',
                                   () => { this._clickAction.release(); });

        this._alternate = alternate;
        this._alternate.connect('notify::visible', this._sync.bind(this));
        if (this._alternate instanceof St.Button)
            this._alternate.connect('clicked',
                                    () => { this._clickAction.release(); });

        this._capturedEventId = global.stage.connect('captured-event', this._onCapturedEvent.bind(this));

        this._flipped = false;

        this._clickAction = new Clutter.ClickAction();
        this._clickAction.connect('long-press', this._onLongPress.bind(this));

        this.actor = new St.Bin();
        this.actor.connect('destroy', this._onDestroy.bind(this));
        this.actor.connect('notify::mapped', () => { this._flipped = false; });
    }

    _sync() {
        let childToShow = null;

        if (this._standard.visible && this._alternate.visible) {
            let [x, y, mods] = global.get_pointer();
            let altPressed = (mods & Clutter.ModifierType.MOD1_MASK) != 0;
            if (this._flipped)
                childToShow = altPressed ? this._standard : this._alternate;
            else
                childToShow = altPressed ? this._alternate : this._standard;
        } else if (this._standard.visible) {
            childToShow = this._standard;
        } else if (this._alternate.visible) {
            childToShow = this._alternate;
        } else {
            this.actor.hide();
            return;
        }

        let childShown = this.actor.get_child();
        if (childShown != childToShow) {
            if (childShown) {
                if (childShown.fake_release)
                    childShown.fake_release();
                childShown.remove_action(this._clickAction);
            }
            childToShow.add_action(this._clickAction);

            let hasFocus = this.actor.contains(global.stage.get_key_focus());
            this.actor.set_child(childToShow);
            if (hasFocus)
                childToShow.grab_key_focus();

            // The actors might respond to hover, so
            // sync the pointer to make sure they update.
            global.sync_pointer();
        }

        this.actor.show();
    }

    _onDestroy() {
        if (this._capturedEventId > 0) {
            global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
    }

    _onCapturedEvent(actor, event) {
        let type = event.type();
        if (type == Clutter.EventType.KEY_PRESS || type == Clutter.EventType.KEY_RELEASE) {
            let key = event.get_key_symbol();
            if (key == Clutter.KEY_Alt_L || key == Clutter.KEY_Alt_R)
                this._sync();
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _onLongPress(action, actor, state) {
        if (state == Clutter.LongPressState.QUERY ||
            state == Clutter.LongPressState.CANCEL)
            return true;

        this._flipped = !this._flipped;
        this._sync();
        return true;
    }
};

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        let userManager = AccountsService.UserManager.get_default();
        this._user = userManager.get_user(GLib.get_user_name());

        this._systemActions = new SystemActions.getDefault();

        this._createSubMenu();

        this._loginScreenItem.actor.connect('notify::visible',
                                            () => { this._updateMultiUser(); });
        this._logoutItem.actor.connect('notify::visible',
                                       () => { this._updateMultiUser(); });
        // Whether shutdown is available or not depends on both lockdown
        // settings (disable-log-out) and Polkit policy - the latter doesn't
        // notify, so we update the menu item each time the menu opens or
        // the lockdown setting changes, which should be close enough.
        this.menu.connect('open-state-changed', (menu, open) => {
            if (!open)
                return;

            this._systemActions.forceUpdate();
        });
        this._updateMultiUser();

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    _updateActionsVisibility() {
        let visible = (this._settingsAction.visible ||
                       this._orientationLockAction.visible ||
                       this._lockScreenAction.visible ||
                       this._altSwitcher.actor.visible);

        this._actionsItem.actor.visible = visible;
    }

    _sessionUpdated() {
        this._settingsAction.visible = Main.sessionMode.allowSettings;
    }

    _updateMultiUser() {
        let hasSwitchUser = this._loginScreenItem.actor.visible;
        let hasLogout = this._logoutItem.actor.visible;

        this._switchUserSubMenu.actor.visible = hasSwitchUser || hasLogout;
    }

    _updateSwitchUserSubMenu() {
        this._switchUserSubMenu.label.text = this._user.get_real_name();
        let clutterText = this._switchUserSubMenu.label.clutter_text;

        // XXX -- for some reason, the ClutterText's width changes
        // rapidly unless we force a relayout of the actor. Probably
        // a size cache issue or something. Moving this to be a layout
        // manager would be a much better idea.
        clutterText.get_allocation_box();

        let layout = clutterText.get_layout();
        if (layout.is_ellipsized())
            this._switchUserSubMenu.label.text = this._user.get_user_name();
    }

    _createActionButton(iconName, accessibleName) {
        let icon = new St.Button({ reactive: true,
                                   can_focus: true,
                                   track_hover: true,
                                   accessible_name: accessibleName,
                                   style_class: 'system-menu-action' });
        icon.child = new St.Icon({ icon_name: iconName });
        return icon;
    }

    _createSubMenu() {
        let bindFlags = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE;
        let item;

        this._switchUserSubMenu = new PopupMenu.PopupSubMenuMenuItem('', true);
        this._switchUserSubMenu.icon.set({
            icon_name: 'avatar-default-symbolic',
            style_class: 'system-switch-user-submenu-icon'
        });

        // Since the label of the switch user submenu depends on the width of
        // the popup menu, and we can't easily connect on allocation-changed
        // or notify::width without creating layout cycles, simply update the
        // label whenever the menu is opened.
        this.menu.connect('open-state-changed', (menu, isOpen) => {
            if (isOpen)
                this._updateSwitchUserSubMenu();
        });

        item = new PopupMenu.PopupMenuItem(_("Switch User"));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateSwitchUser();
        });
        this._switchUserSubMenu.menu.addMenuItem(item);
        this._loginScreenItem = item;
        this._systemActions.bind_property('can-switch-user',
                                          this._loginScreenItem.actor,
                                          'visible',
                                          bindFlags);

        item = new PopupMenu.PopupMenuItem(_("Log Out"));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateLogout();
        });
        this._switchUserSubMenu.menu.addMenuItem(item);
        this._logoutItem = item;
        this._systemActions.bind_property('can-logout',
                                          this._logoutItem.actor,
                                          'visible',
                                          bindFlags);

        this._switchUserSubMenu.menu.addSettingsAction(_("Account Settings"),
                                                       'gnome-user-accounts-panel.desktop');

        this._user.connect('notify::is-loaded', this._updateSwitchUserSubMenu.bind(this));
        this._user.connect('changed', this._updateSwitchUserSubMenu.bind(this));

        this.menu.addMenuItem(this._switchUserSubMenu);

        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

        item = new PopupMenu.PopupBaseMenuItem({ reactive: false,
                                                 can_focus: false });
        this.buttonGroup = item.actor;

        let app = this._settingsApp = Shell.AppSystem.get_default().lookup_app(
            'gnome-control-center.desktop'
        );
        if (app) {
            let [icon, name] = [app.app_info.get_icon().names[0],
                                app.get_name()];
            this._settingsAction = this._createActionButton(icon, name);
            this._settingsAction.connect('clicked',
                                         this._onSettingsClicked.bind(this));
        } else {
            log('Missing required core component Settings, expect trouble…');
            this._settingsAction = new St.Widget();
        }
        item.actor.add(this._settingsAction, { expand: true, x_fill: false });

        this._orientationLockAction = this._createActionButton('', _("Orientation Lock"));
        this._orientationLockAction.connect('clicked', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE),
            this._systemActions.activateLockOrientation();
        });
        item.actor.add(this._orientationLockAction, { expand: true, x_fill: false });
        this._systemActions.bind_property('can-lock-orientation',
                                          this._orientationLockAction,
                                          'visible',
                                          bindFlags);
        this._systemActions.bind_property('orientation-lock-icon',
                                          this._orientationLockAction.child,
                                          'icon-name',
                                          bindFlags);

        this._lockScreenAction = this._createActionButton('changes-prevent', _("Lock"));
        this._lockScreenAction.connect('clicked', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateLockScreen();
        });
        item.actor.add(this._lockScreenAction, { expand: true, x_fill: false });
        this._systemActions.bind_property('can-lock-screen',
                                          this._lockScreenAction,
                                          'visible',
                                          bindFlags);

        this._suspendAction = this._createActionButton('media-playback-pause', _("Suspend"));
        this._suspendAction.connect('clicked', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateSuspend();
        });
        this._systemActions.bind_property('can-suspend',
                                          this._suspendAction,
                                          'visible',
                                          bindFlags);

        this._powerOffAction = this._createActionButton('system-shutdown', _("Power Off"));
        this._powerOffAction.connect('clicked', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activatePowerOff();
        });
        this._systemActions.bind_property('can-power-off',
                                          this._powerOffAction,
                                          'visible',
                                          bindFlags);

        this._altSwitcher = new AltSwitcher(this._powerOffAction, this._suspendAction);
        item.actor.add(this._altSwitcher.actor, { expand: true, x_fill: false });

        this._actionsItem = item;
        this.menu.addMenuItem(item);


        this._settingsAction.connect('notify::visible',
                                     () => { this._updateActionsVisibility(); });
        this._orientationLockAction.connect('notify::visible',
                                            () => { this._updateActionsVisibility(); });
        this._lockScreenAction.connect('notify::visible',
                                       () => { this._updateActionsVisibility(); });
        this._altSwitcher.actor.connect('notify::visible',
                                        () => { this._updateActionsVisibility(); });
    }

    _onSettingsClicked() {
        this.menu.itemActivated();
        Main.overview.hide();
        this._settingsApp.activate();
    }
};
(uuay)shellEntry.js� // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GObject, Pango, Shell, St } = imports.gi;

const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const Params = imports.misc.params;
const PopupMenu = imports.ui.popupMenu;
const Tweener = imports.ui.tweener;

const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const DISABLE_SHOW_PASSWORD_KEY = 'disable-show-password';

var EntryMenu = class extends PopupMenu.PopupMenu {
    constructor(entry) {
        super(entry, 0, St.Side.TOP);

        this._lockdownSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
        this._lockdownSettings.connect('changed::' + DISABLE_SHOW_PASSWORD_KEY, this._resetPasswordItem.bind(this));

        this._entry = entry;
        this._clipboard = St.Clipboard.get_default();

        // Populate menu
        let item;
        item = new PopupMenu.PopupMenuItem(_("Copy"));
        item.connect('activate', this._onCopyActivated.bind(this));
        this.addMenuItem(item);
        this._copyItem = item;

        item = new PopupMenu.PopupMenuItem(_("Paste"));
        item.connect('activate', this._onPasteActivated.bind(this));
        this.addMenuItem(item);
        this._pasteItem = item;

        this._passwordItem = null;

        Main.uiGroup.add_actor(this.actor);
        this.actor.hide();
    }

    _makePasswordItem() {
        let item = new PopupMenu.PopupMenuItem('');
        item.connect('activate', this._onPasswordActivated.bind(this));
        this.addMenuItem(item);
        this._passwordItem = item;
        this._updatePasswordItem();
    }

    _resetPasswordItem() {
        let passwordDisabled = this._lockdownSettings.get_boolean(DISABLE_SHOW_PASSWORD_KEY);

        if (!this.isPassword || passwordDisabled) {
            if (this._passwordItem) {
                this._passwordItem.destroy();
                this._passwordItem = null;
            }
        } else if (this.isPassword && !passwordDisabled) {
            if (!this._passwordItem)
                this._makePasswordItem();
        }

        if (this.isPassword && passwordDisabled)
            this._entry.clutter_text.set_password_char('\u25cf');
    }

    get isPassword() {
        return this._entry.input_purpose == Clutter.InputContentPurpose.PASSWORD;
    }

    set isPassword(v) {
        if (v == this.isPassword)
            return;

        if (v)
            this._entry.input_purpose = Clutter.InputContentPurpose.PASSWORD;
        else
            this._entry.input_purpose = Clutter.InputContentPurpose.NORMAL;

        this._resetPasswordItem();
    }

    open(animate) {
        this._updatePasteItem();
        this._updateCopyItem();
        if (this._passwordItem)
            this._updatePasswordItem();

        super.open(animate);
        this._entry.add_style_pseudo_class('focus');

        let direction = St.DirectionType.TAB_FORWARD;
        if (!this.actor.navigate_focus(null, direction, false))
            this.actor.grab_key_focus();
    }

    _updateCopyItem() {
        let selection = this._entry.clutter_text.get_selection();
        this._copyItem.setSensitive(!this._entry.clutter_text.password_char &&
                                    selection && selection != '');
    }

    _updatePasteItem() {
        this._clipboard.get_text(St.ClipboardType.CLIPBOARD,
            (clipboard, text) => {
                this._pasteItem.setSensitive(text && text != '');
            });
    }

    _updatePasswordItem() {
        let textHidden = (this._entry.clutter_text.password_char);
        if (textHidden)
            this._passwordItem.label.set_text(_("Show Text"));
        else
            this._passwordItem.label.set_text(_("Hide Text"));
    }

    _onCopyActivated() {
        let selection = this._entry.clutter_text.get_selection();
        this._clipboard.set_text(St.ClipboardType.CLIPBOARD, selection);
    }

    _onPasteActivated() {
        this._clipboard.get_text(St.ClipboardType.CLIPBOARD,
            (clipboard, text) => {
                if (!text)
                    return;
                this._entry.clutter_text.delete_selection();
                let pos = this._entry.clutter_text.get_cursor_position();
                this._entry.clutter_text.insert_text(text, pos);
            });
    }

    _onPasswordActivated() {
        let visible = !!(this._entry.clutter_text.password_char);
        this._entry.clutter_text.set_password_char(visible ? '' : '\u25cf');
    }
};

function _setMenuAlignment(entry, stageX) {
    let [success, entryX, entryY] = entry.transform_stage_point(stageX, 0);
    if (success)
        entry.menu.setSourceAlignment(entryX / entry.width);
};

function _onButtonPressEvent(actor, event, entry) {
    if (entry.menu.isOpen) {
        entry.menu.close(BoxPointer.PopupAnimation.FULL);
        return Clutter.EVENT_STOP;
    } else if (event.get_button() == 3) {
        let [stageX, stageY] = event.get_coords();
        _setMenuAlignment(entry, stageX);
        entry.menu.open(BoxPointer.PopupAnimation.FULL);
        return Clutter.EVENT_STOP;
    }
    return Clutter.EVENT_PROPAGATE;
};

function _onPopup(actor, entry) {
    let [success, textX, textY, lineHeight] = entry.clutter_text.position_to_coords(-1);
    if (success)
        entry.menu.setSourceAlignment(textX / entry.width);
    entry.menu.open(BoxPointer.PopupAnimation.FULL);
};

function addContextMenu(entry, params) {
    if (entry.menu)
        return;

    params = Params.parse (params, { isPassword: false, actionMode: Shell.ActionMode.POPUP });

    entry.menu = new EntryMenu(entry);
    entry.menu.isPassword = params.isPassword;
    entry._menuManager = new PopupMenu.PopupMenuManager({ actor: entry },
                                                        { actionMode: params.actionMode });
    entry._menuManager.addMenu(entry.menu);

    // Add an event handler to both the entry and its clutter_text; the former
    // so padding is included in the clickable area, the latter because the
    // event processing of ClutterText prevents event-bubbling.
    entry.clutter_text.connect('button-press-event', (actor, event) => {
        _onButtonPressEvent(actor, event, entry);
    });
    entry.connect('button-press-event', (actor, event) => {
        _onButtonPressEvent(actor, event, entry);
    });

    entry.connect('popup-menu', actor => { _onPopup(actor, entry); });

    entry.connect('destroy', () => {
        entry.menu.destroy();
        entry.menu = null;
        entry._menuManager = null;
    });
}

var CapsLockWarning = GObject.registerClass(
class CapsLockWarning extends St.Label {
    _init(params) {
        let defaultParams = { style_class: 'caps-lock-warning-label' };
        super._init(Object.assign(defaultParams, params));

        this.text = _('Caps lock is on.');

        this.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this.clutter_text.line_wrap = true;

        this._keymap = Clutter.get_default_backend().get_keymap();
        this._stateChangedId = 0;

        const mappedId = this.connect('notify::mapped', () => {
            if (this.is_mapped()) {
                this._stateChangedId = this._keymap.connect('state-changed',
                    () => this._sync(true));
            } else {
                this._keymap.disconnect(this._stateChangedId);
                this._stateChangedId = 0;
            }

            this._sync(false);
        });

        this.connect('destroy', () => {
            if (this._stateChangedId)
                this._keymap.disconnect(this._stateChangedId);
            this.disconnect(mappedId);
        });
    }

    _sync(animate) {
        let capsLockOn = this._keymap.get_caps_lock_state();

        Tweener.removeTweens(this);

        const naturalHeightSet = this.natural_height_set;
        this.natural_height_set = false;
        let [, height] = this.get_preferred_height(-1);
        this.natural_height_set = naturalHeightSet;

        Tweener.addTween(this, {
            height: capsLockOn ? height : 0,
            opacity: capsLockOn ? 255 : 0,
            time: animate ? 0.2 : 0,
            transition: 'easeOutQuad',
            onComplete: () => {
                if (capsLockOn)
                    this.height = -1;
            },
        });
    }
});
(uuay)__init__.js(const Main = imports.ui.main;

var ComponentManager = class {
    constructor() {
        this._allComponents = {};
        this._enabledComponents = [];

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    _sessionUpdated() {
        let newEnabledComponents = Main.sessionMode.components;

        newEnabledComponents.filter(
            name => this._enabledComponents.indexOf(name) == -1
        ).forEach(name => {
            this._enableComponent(name);
        });

        this._enabledComponents.filter(
            name => newEnabledComponents.indexOf(name) == -1
        ).forEach(name => {
            this._disableComponent(name);
        });

        this._enabledComponents = newEnabledComponents;
    }

    _importComponent(name) {
        let module = imports.ui.components[name];
        return module.Component;
    }

    _ensureComponent(name) {
        let component = this._allComponents[name];
        if (component)
            return component;

	if (Main.sessionMode.isLocked)
	    return null;

        let constructor = this._importComponent(name);
        component = new constructor();
        this._allComponents[name] = component;
        return component;
    }

    _enableComponent(name) {
        let component = this._ensureComponent(name);
	if (component)
            component.enable();
    }

    _disableComponent(name) {
        let component = this._allComponents[name];
        if (component == null)
            return;
        component.disable();
    }
};
(uuay)layout.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;

const Background = imports.ui.background;
const BackgroundMenu = imports.ui.backgroundMenu;
const LoginManager = imports.misc.loginManager;

const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

var STARTUP_ANIMATION_TIME = 0.5;
var KEYBOARD_ANIMATION_TIME = 0.15;
var BACKGROUND_FADE_ANIMATION_TIME = 1.0;

var HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
var HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms

function isPopupMetaWindow(actor) {
    switch(actor.meta_window.get_window_type()) {
    case Meta.WindowType.DROPDOWN_MENU:
    case Meta.WindowType.POPUP_MENU:
    case Meta.WindowType.COMBO:
        return true;
    default:
        return false;
    }
}

var MonitorConstraint = GObject.registerClass({
    Properties: {'primary': GObject.ParamSpec.boolean('primary', 
                                                      'Primary', 'Track primary monitor',
                                                      GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
                                                      false),
                 'index': GObject.ParamSpec.int('index',
                                                'Monitor index', 'Track specific monitor',
                                                GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
                                                -1, 64, -1),
                 'work-area': GObject.ParamSpec.boolean('work-area',
                                                        'Work-area', 'Track monitor\'s work-area',
                                                        GObject.ParamFlags.READABLE | GObject.ParamFlags.WRITABLE,
                                                        false)},
}, class MonitorConstraint extends Clutter.Constraint {
    _init(props) {
        this._primary = false;
        this._index = -1;
        this._workArea = false;

        super._init(props);
    }

    get primary() {
        return this._primary;
    }

    set primary(v) {
        if (v)
            this._index = -1;
        this._primary = v;
        if (this.actor)
            this.actor.queue_relayout();
        this.notify('primary');
    }

    get index() {
        return this._index;
    }

    set index(v) {
        this._primary = false;
        this._index = v;
        if (this.actor)
            this.actor.queue_relayout();
        this.notify('index');
    }

    get work_area() {
        return this._workArea;
    }

    set work_area(v) {
        if (v == this._workArea)
            return;
        this._workArea = v;
        if (this.actor)
            this.actor.queue_relayout();
        this.notify('work-area');
    }

    vfunc_set_actor(actor) {
        if (actor) {
            if (!this._monitorsChangedId) {
                this._monitorsChangedId =
                    Main.layoutManager.connect('monitors-changed', () => {
                        this.actor.queue_relayout();
                    });
            }

            if (!this._workareasChangedId) {
                this._workareasChangedId =
                    global.display.connect('workareas-changed', () => {
                        if (this._workArea)
                            this.actor.queue_relayout();
                    });
            }
        } else {
            if (this._monitorsChangedId)
                Main.layoutManager.disconnect(this._monitorsChangedId);
            this._monitorsChangedId = 0;

            if (this._workareasChangedId)
                global.display.disconnect(this._workareasChangedId);
            this._workareasChangedId = 0;
        }

        super.vfunc_set_actor(actor);
    }

    vfunc_update_allocation(actor, actorBox) {
        if (!this._primary && this._index < 0)
            return;

        if (!Main.layoutManager.primaryMonitor)
            return;

        let index;
        if (this._primary)
            index = Main.layoutManager.primaryIndex;
        else
            index = Math.min(this._index, Main.layoutManager.monitors.length - 1);

        let rect;
        if (this._workArea) {
            let workspaceManager = global.workspace_manager;
            let ws = workspaceManager.get_workspace_by_index(0);
            rect = ws.get_work_area_for_monitor(index);
        } else {
            rect = Main.layoutManager.monitors[index];
        }

        actorBox.init_rect(rect.x, rect.y, rect.width, rect.height);
    }
});

var Monitor = class Monitor {
    constructor(index, geometry, geometry_scale) {
        this.index = index;
        this.x = geometry.x;
        this.y = geometry.y;
        this.width = geometry.width;
        this.height = geometry.height;
        this.geometry_scale = geometry_scale;
    }

    get inFullscreen() {
        return global.display.get_monitor_in_fullscreen(this.index);
    }
};

const UiActor = GObject.registerClass(
class UiActor extends St.Widget {
    vfunc_get_preferred_width (forHeight) {
        let width = global.stage.width;
        return [width, width];
    }

    vfunc_get_preferred_height (forWidth) {
        let height = global.stage.height;
        return [height, height];
    }
});

const defaultParams = {
    trackFullscreen: false,
    affectsStruts: false,
    affectsInputRegion: true
};

var LayoutManager = GObject.registerClass({
    Signals: { 'hot-corners-changed': {},
               'startup-complete': {},
               'startup-prepared': {},
               'monitors-changed': {},
               'keyboard-visible-changed': { param_types: [GObject.TYPE_BOOLEAN] } },
}, class LayoutManager extends GObject.Object {
    _init() {
        super._init();

        this._rtl = (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL);
        this.monitors = [];
        this.primaryMonitor = null;
        this.primaryIndex = -1;
        this.hotCorners = [];

        this._keyboardIndex = -1;
        this._rightPanelBarrier = null;

        this._inOverview = false;
        this._updateRegionIdle = 0;

        this._trackedActors = [];
        this._topActors = [];
        this._isPopupWindowVisible = false;
        this._startingUp = true;
        this._pendingLoadBackground = false;

        // We don't want to paint the stage background color because either
        // the SystemBackground we create or the MetaBackgroundActor inside
        // global.window_group covers the entirety of the screen.
        global.stage.no_clear_hint = true;

        // Set up stage hierarchy to group all UI actors under one container.
        this.uiGroup = new UiActor({ name: 'uiGroup' });
        this.uiGroup.set_flags(Clutter.ActorFlags.NO_LAYOUT);

        global.stage.remove_actor(global.window_group);
        this.uiGroup.add_actor(global.window_group);

        global.stage.add_child(this.uiGroup);

        this.overviewGroup = new St.Widget({ name: 'overviewGroup',
                                             visible: false,
                                             reactive: true });
        this.addChrome(this.overviewGroup);

        this.screenShieldGroup = new St.Widget({ name: 'screenShieldGroup',
                                                 visible: false,
                                                 clip_to_allocation: true,
                                                 layout_manager: new Clutter.BinLayout(),
                                               });
        this.addChrome(this.screenShieldGroup);

        this.panelBox = new St.BoxLayout({ name: 'panelBox',
                                           vertical: true });
        this.addChrome(this.panelBox, { affectsStruts: true,
                                        trackFullscreen: true });
        this.panelBox.connect('allocation-changed',
                              this._panelBoxChanged.bind(this));

        this.modalDialogGroup = new St.Widget({ name: 'modalDialogGroup',
                                                layout_manager: new Clutter.BinLayout() });
        this.uiGroup.add_actor(this.modalDialogGroup);

        this.keyboardBox = new St.BoxLayout({ name: 'keyboardBox',
                                              reactive: true,
                                              track_hover: true });
        this.addChrome(this.keyboardBox);
        this._keyboardHeightNotifyId = 0;

        // A dummy actor that tracks the mouse or text cursor, based on the
        // position and size set in setDummyCursorGeometry.
        this.dummyCursor = new St.Widget({ width: 0, height: 0, opacity: 0 });
        this.uiGroup.add_actor(this.dummyCursor);

        global.stage.remove_actor(global.top_window_group);
        this.uiGroup.add_actor(global.top_window_group);

        let feedbackGroup = Meta.get_feedback_group_for_display(global.display);
        global.stage.remove_actor(feedbackGroup);
        this.uiGroup.add_actor(feedbackGroup);

        this._backgroundGroup = new Meta.BackgroundGroup();
        global.window_group.add_child(this._backgroundGroup);
        this._backgroundGroup.lower_bottom();
        this._bgManagers = [];

        this._interfaceSettings = new Gio.Settings({
            schema_id: 'org.gnome.desktop.interface'
        });

       this._interfaceSettings.connect('changed::enable-hot-corners',
                                       this._updateHotCorners.bind(this));

        // Need to update struts on new workspaces when they are added
        let workspaceManager = global.workspace_manager;
        workspaceManager.connect('notify::n-workspaces',
                                 this._queueUpdateRegions.bind(this));

        let display = global.display;
        display.connect('restacked',
                        this._windowsRestacked.bind(this));
        display.connect('in-fullscreen-changed',
                        this._updateFullscreen.bind(this));

        let monitorManager = Meta.MonitorManager.get();
        monitorManager.connect('monitors-changed',
                               this._monitorsChanged.bind(this));
        this._monitorsChanged();
    }

    // This is called by Main after everything else is constructed
    init() {
        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));

        this._loadBackground();
    }

    showOverview() {
        this.overviewGroup.show();

        this._inOverview = true;
        this._updateVisibility();
    }

    hideOverview() {
        this.overviewGroup.hide();

        this._inOverview = false;
        this._updateVisibility();
    }

    _sessionUpdated() {
        this._updateVisibility();
        this._queueUpdateRegions();
    }

    _updateMonitors() {
        let display = global.display;

        this.monitors = [];
        let nMonitors = display.get_n_monitors();
        for (let i = 0; i < nMonitors; i++)
            this.monitors.push(new Monitor(i,
                                           display.get_monitor_geometry(i),
                                           display.get_monitor_scale(i)));

        if (nMonitors == 0) {
            this.primaryIndex = this.bottomIndex = -1;
        } else if (nMonitors == 1) {
            this.primaryIndex = this.bottomIndex = 0;
        } else {
            // If there are monitors below the primary, then we need
            // to split primary from bottom.
            this.primaryIndex = this.bottomIndex = display.get_primary_monitor();
            for (let i = 0; i < this.monitors.length; i++) {
                let monitor = this.monitors[i];
                if (this._isAboveOrBelowPrimary(monitor)) {
                    if (monitor.y > this.monitors[this.bottomIndex].y)
                        this.bottomIndex = i;
                }
            }
        }
        if (this.primaryIndex != -1) {
            this.primaryMonitor = this.monitors[this.primaryIndex];
            this.bottomMonitor = this.monitors[this.bottomIndex];

            if (this._pendingLoadBackground) {
                this._loadBackground();
                this._pendingLoadBackground = false;
            }
        } else {
            this.primaryMonitor = null;
            this.bottomMonitor = null;
        }
    }

    _updateHotCorners() {
        // destroy old hot corners
        this.hotCorners.forEach(corner => {
            if (corner)
                corner.destroy();
        });
        this.hotCorners = [];

        if (!this._interfaceSettings.get_boolean('enable-hot-corners')) {
            this.emit('hot-corners-changed');
            return;
        }

        let size = this.panelBox.height;

        // build new hot corners
        for (let i = 0; i < this.monitors.length; i++) {
            let monitor = this.monitors[i];
            let cornerX = this._rtl ? monitor.x + monitor.width : monitor.x;
            let cornerY = monitor.y;

            let haveTopLeftCorner = true;

            if (i != this.primaryIndex) {
                // Check if we have a top left (right for RTL) corner.
                // I.e. if there is no monitor directly above or to the left(right)
                let besideX = this._rtl ? monitor.x + 1 : cornerX - 1;
                let besideY = cornerY;
                let aboveX = cornerX;
                let aboveY = cornerY - 1;

                for (let j = 0; j < this.monitors.length; j++) {
                    if (i == j)
                        continue;
                    let otherMonitor = this.monitors[j];
                    if (besideX >= otherMonitor.x &&
                        besideX < otherMonitor.x + otherMonitor.width &&
                        besideY >= otherMonitor.y &&
                        besideY < otherMonitor.y + otherMonitor.height) {
                        haveTopLeftCorner = false;
                        break;
                    }
                    if (aboveX >= otherMonitor.x &&
                        aboveX < otherMonitor.x + otherMonitor.width &&
                        aboveY >= otherMonitor.y &&
                        aboveY < otherMonitor.y + otherMonitor.height) {
                        haveTopLeftCorner = false;
                        break;
                    }
                }
            }

            if (haveTopLeftCorner) {
                let corner = new HotCorner(this, monitor, cornerX, cornerY);
                corner.setBarrierSize(size);
                this.hotCorners.push(corner);
            } else {
                this.hotCorners.push(null);
            }
        }

        this.emit('hot-corners-changed');
    }

    _addBackgroundMenu(bgManager) {
        BackgroundMenu.addBackgroundMenu(bgManager.backgroundActor, this);
    }

    _createBackgroundManager(monitorIndex) {
        let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
                                                           layoutManager: this,
                                                           monitorIndex: monitorIndex });

        bgManager.connect('changed', this._addBackgroundMenu.bind(this));
        this._addBackgroundMenu(bgManager);

        return bgManager;
    }

    _showSecondaryBackgrounds() {
        for (let i = 0; i < this.monitors.length; i++) {
            if (i != this.primaryIndex) {
                let backgroundActor = this._bgManagers[i].backgroundActor;
                backgroundActor.show();
                backgroundActor.opacity = 0;
                Tweener.addTween(backgroundActor,
                                 { opacity: 255,
                                   time: BACKGROUND_FADE_ANIMATION_TIME,
                                   transition: 'easeOutQuad' });
            }
        }
    }

    _updateBackgrounds() {
        let i;
        for (i = 0; i < this._bgManagers.length; i++)
            this._bgManagers[i].destroy();

        this._bgManagers = [];

        if (Main.sessionMode.isGreeter)
            return;

        for (let i = 0; i < this.monitors.length; i++) {
            let bgManager = this._createBackgroundManager(i);
            this._bgManagers.push(bgManager);

            if (i != this.primaryIndex && this._startingUp)
                bgManager.backgroundActor.hide();
        }
    }

    _updateKeyboardBox() {
        this.keyboardBox.set_position(this.keyboardMonitor.x,
                                      this.keyboardMonitor.y + this.keyboardMonitor.height);
        this.keyboardBox.set_size(this.keyboardMonitor.width, -1);
    }

    _updateBoxes() {
        this.screenShieldGroup.set_position(0, 0);
        this.screenShieldGroup.set_size(global.screen_width, global.screen_height);

        if (!this.primaryMonitor)
            return;

        this.panelBox.set_position(this.primaryMonitor.x, this.primaryMonitor.y);
        this.panelBox.set_size(this.primaryMonitor.width, -1);

        this.keyboardIndex = this.primaryIndex;
    }

    _panelBoxChanged() {
        this._updatePanelBarrier();

        let size = this.panelBox.height;
        this.hotCorners.forEach(corner => {
            if (corner)
                corner.setBarrierSize(size);
        });
    }

    _updatePanelBarrier() {
        if (this._rightPanelBarrier) {
            this._rightPanelBarrier.destroy();
            this._rightPanelBarrier = null;
        }

        if (!this.primaryMonitor)
            return;

        if (this.panelBox.height) {
            let primary = this.primaryMonitor;

            this._rightPanelBarrier = new Meta.Barrier({ display: global.display,
                                                         x1: primary.x + primary.width, y1: primary.y,
                                                         x2: primary.x + primary.width, y2: primary.y + this.panelBox.height,
                                                         directions: Meta.BarrierDirection.NEGATIVE_X });
        }
    }

    _monitorsChanged() {
        this._updateMonitors();
        this._updateBoxes();
        this._updateHotCorners();
        this._updateBackgrounds();
        this._updateFullscreen();
        this._updateVisibility();
        this._queueUpdateRegions();

        this.emit('monitors-changed');
    }

    _isAboveOrBelowPrimary(monitor) {
        let primary = this.monitors[this.primaryIndex];
        let monitorLeft = monitor.x, monitorRight = monitor.x + monitor.width;
        let primaryLeft = primary.x, primaryRight = primary.x + primary.width;

        if ((monitorLeft >= primaryLeft && monitorLeft < primaryRight) ||
            (monitorRight > primaryLeft && monitorRight <= primaryRight) ||
            (primaryLeft >= monitorLeft && primaryLeft < monitorRight) ||
            (primaryRight > monitorLeft && primaryRight <= monitorRight))
            return true;

        return false;
    }

    get currentMonitor() {
        let index = global.display.get_current_monitor();
        return this.monitors[index];
    }

    get keyboardMonitor() {
        return this.monitors[this.keyboardIndex];
    }

    get focusIndex() {
        let i = Main.layoutManager.primaryIndex;

        if (global.stage.key_focus != null)
            i = this.findIndexForActor(global.stage.key_focus);
        else if (global.display.focus_window != null)
            i = global.display.focus_window.get_monitor();
        return i;
    }

    get focusMonitor() {
        if (this.focusIndex < 0)
            return null;
        return this.monitors[this.focusIndex];
    }

    set keyboardIndex(v) {
        this._keyboardIndex = v;
        this._updateKeyboardBox();
    }

    get keyboardIndex() {
        return this._keyboardIndex;
    }

    _loadBackground() {
        if (!this.primaryMonitor) {
            this._pendingLoadBackground = true;
            return;
        }
        this._systemBackground = new Background.SystemBackground();
        this._systemBackground.actor.hide();

        global.stage.insert_child_below(this._systemBackground.actor, null);

        let constraint = new Clutter.BindConstraint({ source: global.stage,
                                                      coordinate: Clutter.BindCoordinate.ALL });
        this._systemBackground.actor.add_constraint(constraint);

        let signalId = this._systemBackground.connect('loaded', () => {
            this._systemBackground.disconnect(signalId);
            this._systemBackground.actor.show();
            global.stage.show();

            this._prepareStartupAnimation();
        });
    }

    // Startup Animations
    //
    // We have two different animations, depending on whether we're a greeter
    // or a normal session.
    //
    // In the greeter, we want to animate the panel from the top, and smoothly
    // fade the login dialog on top of whatever plymouth left on screen which
    // we get as a still frame background before drawing anything else.
    //
    // Here we just have the code to animate the panel, and fade up the background.
    // The login dialog animation is handled by modalDialog.js
    //
    // When starting a normal user session, we want to grow it out of the middle
    // of the screen.

    _prepareStartupAnimation() {
        // During the initial transition, add a simple actor to block all events,
        // so they don't get delivered to X11 windows that have been transformed.
        this._coverPane = new Clutter.Actor({ opacity: 0,
                                              width: global.screen_width,
                                              height: global.screen_height,
                                              reactive: true });
        this.addChrome(this._coverPane);

        // Force an update of the regions before we scale the UI group to
        // get the correct allocation for the struts.
        // Do this even when we don't animate on restart, so that maximized
        // windows restore to the right size.
        this._updateRegions();

        if (Meta.is_restart()) {
            // On restart, we don't do an animation.
        } else if (Main.sessionMode.isGreeter) {
            this.panelBox.translation_y = -this.panelBox.height;
        } else {
            this._updateBackgrounds();

            this.keyboardBox.hide();

            let monitor = this.primaryMonitor;
            let x = monitor.x + monitor.width / 2.0;
            let y = monitor.y + monitor.height / 2.0;

            this.uiGroup.set_pivot_point(x / global.screen_width,
                                         y / global.screen_height);
            this.uiGroup.scale_x = this.uiGroup.scale_y = 0.75;
            this.uiGroup.opacity = 0;
            global.window_group.set_clip(monitor.x, monitor.y, monitor.width, monitor.height);
        }

        this.emit('startup-prepared');

        // We're mostly prepared for the startup animation
        // now, but since a lot is going on asynchronously
        // during startup, let's defer the startup animation
        // until the event loop is uncontended and idle.
        // This helps to prevent us from running the animation
        // when the system is bogged down
        let id = GLib.idle_add(GLib.PRIORITY_LOW, () => {
            this._startupAnimation();
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(id, '[gnome-shell] this._startupAnimation');
    }

    _startupAnimation() {
        if (Meta.is_restart())
            this._startupAnimationComplete();
        else if (Main.sessionMode.isGreeter)
            this._startupAnimationGreeter();
        else
            this._startupAnimationSession();
    }

    _startupAnimationGreeter() {
        Tweener.addTween(this.panelBox,
                         { translation_y: 0,
                           time: STARTUP_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._startupAnimationComplete,
                           onCompleteScope: this });
    }

    _startupAnimationSession() {
        Tweener.addTween(this.uiGroup,
                         { scale_x: 1,
                           scale_y: 1,
                           opacity: 255,
                           time: STARTUP_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._startupAnimationComplete,
                           onCompleteScope: this });
    }

    _startupAnimationComplete() {
        this._coverPane.destroy();
        this._coverPane = null;

        this._systemBackground.actor.destroy();
        this._systemBackground = null;

        this._startingUp = false;

        this.keyboardBox.show();

        if (!Main.sessionMode.isGreeter) {
            this._showSecondaryBackgrounds();
            global.window_group.remove_clip();
        }

        this._queueUpdateRegions();

        this.emit('startup-complete');
    }

    showKeyboard() {
        this.keyboardBox.show();
        Tweener.addTween(this.keyboardBox,
                         { translation_y: -this.keyboardBox.height,
                           opacity: 255,
                           time: KEYBOARD_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._showKeyboardComplete,
                           onCompleteScope: this
                         });
        this.emit('keyboard-visible-changed', true);
    }

    _showKeyboardComplete() {
        // Poke Chrome to update the input shape; it doesn't notice
        // anchor point changes
        this._updateRegions();

        this._keyboardHeightNotifyId = this.keyboardBox.connect('notify::height', () => {
            this.keyboardBox.translation_y = -this.keyboardBox.height;
        });
    }

    hideKeyboard(immediate) {
        if (this._keyboardHeightNotifyId) {
            this.keyboardBox.disconnect(this._keyboardHeightNotifyId);
            this._keyboardHeightNotifyId = 0;
        }
        Tweener.addTween(this.keyboardBox,
                         { translation_y: 0,
                           opacity: 0,
                           time: immediate ? 0 : KEYBOARD_ANIMATION_TIME,
                           transition: 'easeInQuad',
                           onComplete: this._hideKeyboardComplete,
                           onCompleteScope: this
                         });

        this.emit('keyboard-visible-changed', false);
    }

    _hideKeyboardComplete() {
        this.keyboardBox.hide();
        this._updateRegions();
        global.stage.queue_redraw();
    }

    // setDummyCursorGeometry:
    //
    // The cursor dummy is a standard widget commonly used for popup
    // menus and box pointers to track, as the box pointer API only
    // tracks actors. If you want to pop up a menu based on where the
    // user clicked, or where the text cursor is, the cursor dummy
    // is what you should use. Given that the menu should not track
    // the actual mouse pointer as it moves, you need to call this
    // function before you show the menu to ensure it is at the right
    // position and has the right size.
    setDummyCursorGeometry(x, y, w, h) {
        this.dummyCursor.set_position(Math.round(x), Math.round(y));
        this.dummyCursor.set_size(Math.round(w), Math.round(h));
    }

    // addChrome:
    // @actor: an actor to add to the chrome
    // @params: (optional) additional params
    //
    // Adds @actor to the chrome, and (unless %affectsInputRegion in
    // @params is %false) extends the input region to include it.
    // Changes in @actor's size, position, and visibility will
    // automatically result in appropriate changes to the input
    // region.
    //
    // If %affectsStruts in @params is %true (and @actor is along a
    // screen edge), then @actor's size and position will also affect
    // the window manager struts. Changes to @actor's visibility will
    // NOT affect whether or not the strut is present, however.
    //
    // If %trackFullscreen in @params is %true, the actor's visibility
    // will be bound to the presence of fullscreen windows on the same
    // monitor (it will be hidden whenever a fullscreen window is visible,
    // and shown otherwise)
    addChrome(actor, params) {
        this.uiGroup.add_actor(actor);
        if (this.uiGroup.contains(global.top_window_group))
            this.uiGroup.set_child_below_sibling(actor, global.top_window_group);
        this._trackActor(actor, params);
    }

    // trackChrome:
    // @actor: a descendant of the chrome to begin tracking
    // @params: parameters describing how to track @actor
    //
    // Tells the chrome to track @actor. This can be used to extend the
    // struts or input region to cover specific children.
    //
    // @params can have any of the same values as in addChrome(),
    // though some possibilities don't make sense. By default, @actor has
    // the same params as its chrome ancestor.
    trackChrome(actor, params) {
        let ancestor = actor.get_parent();
        let index = this._findActor(ancestor);
        while (ancestor && index == -1) {
            ancestor = ancestor.get_parent();
            index = this._findActor(ancestor);
        }

        let ancestorData = ancestor ? this._trackedActors[index]
                                    : defaultParams;
        if (!params)
            params = {};
        // We can't use Params.parse here because we want to drop
        // the extra values like ancestorData.actor
        for (let prop in defaultParams) {
            if (!params.hasOwnProperty(prop))
                params[prop] = ancestorData[prop];
        }

        this._trackActor(actor, params);
    }

    // untrackChrome:
    // @actor: an actor previously tracked via trackChrome()
    //
    // Undoes the effect of trackChrome()
    untrackChrome(actor) {
        this._untrackActor(actor);
    }

    // removeChrome:
    // @actor: a chrome actor
    //
    // Removes @actor from the chrome
    removeChrome(actor) {
        this.uiGroup.remove_actor(actor);
        this._untrackActor(actor);
    }

    _findActor(actor) {
        for (let i = 0; i < this._trackedActors.length; i++) {
            let actorData = this._trackedActors[i];
            if (actorData.actor == actor)
                return i;
        }
        return -1;
    }

    _trackActor(actor, params) {
        if (this._findActor(actor) != -1)
            throw new Error('trying to re-track existing chrome actor');

        let actorData = Params.parse(params, defaultParams);
        actorData.actor = actor;
        actorData.visibleId = actor.connect('notify::visible',
                                            this._queueUpdateRegions.bind(this));
        actorData.allocationId = actor.connect('notify::allocation',
                                               this._queueUpdateRegions.bind(this));
        actorData.destroyId = actor.connect('destroy',
                                            this._untrackActor.bind(this));
        // Note that destroying actor will unset its parent, so we don't
        // need to connect to 'destroy' too.

        this._trackedActors.push(actorData);
        this._updateActorVisibility(actorData);
        this._queueUpdateRegions();
    }

    _untrackActor(actor) {
        let i = this._findActor(actor);

        if (i == -1)
            return;
        let actorData = this._trackedActors[i];

        this._trackedActors.splice(i, 1);
        actor.disconnect(actorData.visibleId);
        actor.disconnect(actorData.allocationId);
        actor.disconnect(actorData.destroyId);

        this._queueUpdateRegions();
    }

    _updateActorVisibility(actorData) {
        if (!actorData.trackFullscreen)
            return;

        let monitor = this.findMonitorForActor(actorData.actor);
        actorData.actor.visible = !(global.window_group.visible &&
                                    monitor &&
                                    monitor.inFullscreen);
    }

    _updateVisibility() {
        let windowsVisible = Main.sessionMode.hasWindows && !this._inOverview;

        global.window_group.visible = windowsVisible;
        global.top_window_group.visible = windowsVisible;

        this._trackedActors.forEach(this._updateActorVisibility.bind(this));
    }

    getWorkAreaForMonitor(monitorIndex) {
        // Assume that all workspaces will have the same
        // struts and pick the first one.
        let workspaceManager = global.workspace_manager;
        let ws = workspaceManager.get_workspace_by_index(0);
        return ws.get_work_area_for_monitor(monitorIndex);
    }

    // This call guarantees that we return some monitor to simplify usage of it
    // In practice all tracked actors should be visible on some monitor anyway
    findIndexForActor(actor) {
        let [x, y] = actor.get_transformed_position();
        let [w, h] = actor.get_transformed_size();
        let rect = new Meta.Rectangle({ x: x, y: y, width: w, height: h });
        return global.display.get_monitor_index_for_rect(rect);
    }

    findMonitorForActor(actor) {
        let index = this.findIndexForActor(actor);
        if (index >= 0 && index < this.monitors.length)
            return this.monitors[index];
        return null;
    }

    _queueUpdateRegions() {
        if (this._startingUp)
            return;

        if (!this._updateRegionIdle)
            this._updateRegionIdle = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
                                                    this._updateRegions.bind(this));
    }

    _getWindowActorsForWorkspace(workspace) {
        return global.get_window_actors().filter(actor => {
            let win = actor.meta_window;
            return win.located_on_workspace(workspace);
        });
    }

    _updateFullscreen() {
        this._updateVisibility();
        this._queueUpdateRegions();
    }

    _windowsRestacked() {
        let changed = false;

        if (this._isPopupWindowVisible != global.top_window_group.get_children().some(isPopupMetaWindow))
            changed = true;

        if (changed) {
            this._updateVisibility();
            this._queueUpdateRegions();
        }
    }

    _updateRegions() {
        if (this._updateRegionIdle) {
            Meta.later_remove(this._updateRegionIdle);
            delete this._updateRegionIdle;
        }

        // No need to update when we have a modal.
        if (Main.modalCount > 0)
            return GLib.SOURCE_REMOVE;

        // Bug workaround - get_transformed_position()/get_transformed_size() don't work after
        // a change in stage size until the first pick or paint.
        // https://bugzilla.gnome.org/show_bug.cgi?id=761565
        global.stage.get_actor_at_pos(Clutter.PickMode.ALL, 0, 0);

        let rects = [], struts = [], i;
        let isPopupMenuVisible = global.top_window_group.get_children().some(isPopupMetaWindow);
        let wantsInputRegion = !isPopupMenuVisible;

        for (i = 0; i < this._trackedActors.length; i++) {
            let actorData = this._trackedActors[i];
            if (!(actorData.affectsInputRegion && wantsInputRegion) && !actorData.affectsStruts)
                continue;

            let [x, y] = actorData.actor.get_transformed_position();
            let [w, h] = actorData.actor.get_transformed_size();
            x = Math.round(x);
            y = Math.round(y);
            w = Math.round(w);
            h = Math.round(h);

            if (actorData.affectsInputRegion && wantsInputRegion && actorData.actor.get_paint_visibility())
                rects.push(new Meta.Rectangle({ x: x, y: y, width: w, height: h }));

            let monitor = null;
            if (actorData.affectsStruts)
                monitor = this.findMonitorForActor(actorData.actor);

            if (monitor) {
                // Limit struts to the size of the screen
                let x1 = Math.max(x, 0);
                let x2 = Math.min(x + w, global.screen_width);
                let y1 = Math.max(y, 0);
                let y2 = Math.min(y + h, global.screen_height);

                // Metacity wants to know what side of the monitor the
                // strut is considered to be attached to. First, we find
                // the monitor that contains the strut. If the actor is
                // only touching one edge, or is touching the entire
                // border of that monitor, then it's obvious which side
                // to call it. If it's in a corner, we pick a side
                // arbitrarily. If it doesn't touch any edges, or it
                // spans the width/height across the middle of the
                // screen, then we don't create a strut for it at all.

                let side;
                if (x1 <= monitor.x && x2 >= monitor.x + monitor.width) {
                    if (y1 <= monitor.y)
                        side = Meta.Side.TOP;
                    else if (y2 >= monitor.y + monitor.height)
                        side = Meta.Side.BOTTOM;
                    else
                        continue;
                } else if (y1 <= monitor.y && y2 >= monitor.y + monitor.height) {
                    if (x1 <= monitor.x)
                        side = Meta.Side.LEFT;
                    else if (x2 >= monitor.x + monitor.width)
                        side = Meta.Side.RIGHT;
                    else
                        continue;
                } else if (x1 <= monitor.x)
                    side = Meta.Side.LEFT;
                else if (y1 <= monitor.y)
                    side = Meta.Side.TOP;
                else if (x2 >= monitor.x + monitor.width)
                    side = Meta.Side.RIGHT;
                else if (y2 >= monitor.y + monitor.height)
                    side = Meta.Side.BOTTOM;
                else
                    continue;

                let strutRect = new Meta.Rectangle({ x: x1, y: y1, width: x2 - x1, height: y2 - y1});
                let strut = new Meta.Strut({ rect: strutRect, side: side });
                struts.push(strut);
            }
        }

        if (!Meta.is_wayland_compositor())
            global.set_stage_input_region(rects);
        this._isPopupWindowVisible = isPopupMenuVisible;

        let workspaceManager = global.workspace_manager;
        for (let w = 0; w < workspaceManager.n_workspaces; w++) {
            let workspace = workspaceManager.get_workspace_by_index(w);
            workspace.set_builtin_struts(struts);
        }

        return GLib.SOURCE_REMOVE;
    }

    modalEnded() {
        // We don't update the stage input region while in a modal,
        // so queue an update now.
        this._queueUpdateRegions();
    }
});


// HotCorner:
//
// This class manages a "hot corner" that can toggle switching to
// overview.
var HotCorner = class HotCorner {
    constructor(layoutManager, monitor, x, y) {
        // We use this flag to mark the case where the user has entered the
        // hot corner and has not left both the hot corner and a surrounding
        // guard area (the "environs"). This avoids triggering the hot corner
        // multiple times due to an accidental jitter.
        this._entered = false;

        this._monitor = monitor;

        this._x = x;
        this._y = y;

        this._setupFallbackCornerIfNeeded(layoutManager);

        this._pressureBarrier = new PressureBarrier(HOT_CORNER_PRESSURE_THRESHOLD,
                                                    HOT_CORNER_PRESSURE_TIMEOUT,
                                                    Shell.ActionMode.NORMAL |
                                                    Shell.ActionMode.OVERVIEW);
        this._pressureBarrier.connect('trigger', this._toggleOverview.bind(this));

        // Cache the three ripples instead of dynamically creating and destroying them.
        this._ripple1 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
        this._ripple2 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });
        this._ripple3 = new St.BoxLayout({ style_class: 'ripple-box', opacity: 0, visible: false });

        layoutManager.uiGroup.add_actor(this._ripple1);
        layoutManager.uiGroup.add_actor(this._ripple2);
        layoutManager.uiGroup.add_actor(this._ripple3);
    }

    setBarrierSize(size) {
        if (this._verticalBarrier) {
            this._pressureBarrier.removeBarrier(this._verticalBarrier);
            this._verticalBarrier.destroy();
            this._verticalBarrier = null;
        }

        if (this._horizontalBarrier) {
            this._pressureBarrier.removeBarrier(this._horizontalBarrier);
            this._horizontalBarrier.destroy();
            this._horizontalBarrier = null;
        }

        if (size > 0) {
            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
                this._verticalBarrier = new Meta.Barrier({ display: global.display,
                                                           x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
                                                           directions: Meta.BarrierDirection.NEGATIVE_X });
                this._horizontalBarrier = new Meta.Barrier({ display: global.display,
                                                             x1: this._x - size, x2: this._x, y1: this._y, y2: this._y,
                                                             directions: Meta.BarrierDirection.POSITIVE_Y });
            } else {
                this._verticalBarrier = new Meta.Barrier({ display: global.display,
                                                           x1: this._x, x2: this._x, y1: this._y, y2: this._y + size,
                                                           directions: Meta.BarrierDirection.POSITIVE_X });
                this._horizontalBarrier = new Meta.Barrier({ display: global.display,
                                                             x1: this._x, x2: this._x + size, y1: this._y, y2: this._y,
                                                             directions: Meta.BarrierDirection.POSITIVE_Y });
            }

            this._pressureBarrier.addBarrier(this._verticalBarrier);
            this._pressureBarrier.addBarrier(this._horizontalBarrier);
        }
    }

    _setupFallbackCornerIfNeeded(layoutManager) {
        if (!global.display.supports_extended_barriers()) {
            this.actor = new Clutter.Actor({ name: 'hot-corner-environs',
                                             x: this._x, y: this._y,
                                             width: 3,
                                             height: 3,
                                             reactive: true });

            this._corner = new Clutter.Actor({ name: 'hot-corner',
                                               width: 1,
                                               height: 1,
                                               opacity: 0,
                                               reactive: true });
            this._corner._delegate = this;

            this.actor.add_child(this._corner);
            layoutManager.addChrome(this.actor);

            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
                this._corner.set_position(this.actor.width - this._corner.width, 0);
                this.actor.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);
            } else {
                this._corner.set_position(0, 0);
            }

            this.actor.connect('leave-event',
                               this._onEnvironsLeft.bind(this));

            this._corner.connect('enter-event',
                                 this._onCornerEntered.bind(this));
            this._corner.connect('leave-event',
                                 this._onCornerLeft.bind(this));
        }
    }

    destroy() {
        this.setBarrierSize(0);
        this._pressureBarrier.destroy();
        this._pressureBarrier = null;

        if (this.actor)
            this.actor.destroy();
    }

    _animRipple(ripple, delay, time, startScale, startOpacity, finalScale) {
        // We draw a ripple by using a source image and animating it scaling
        // outwards and fading away. We want the ripples to move linearly
        // or it looks unrealistic, but if the opacity of the ripple goes
        // linearly to zero it fades away too quickly, so we use Tweener's
        // 'onUpdate' to give a non-linear curve to the fade-away and make
        // it more visible in the middle section.

        ripple._opacity = startOpacity;

        if (ripple.get_text_direction() == Clutter.TextDirection.RTL)
            ripple.set_anchor_point_from_gravity(Clutter.Gravity.NORTH_EAST);

        ripple.visible = true;
        ripple.opacity = 255 * Math.sqrt(startOpacity);
        ripple.scale_x = ripple.scale_y = startScale;

        ripple.x = this._x;
        ripple.y = this._y;

        Tweener.addTween(ripple, { _opacity: 0,
                                   scale_x: finalScale,
                                   scale_y: finalScale,
                                   delay: delay,
                                   time: time,
                                   transition: 'linear',
                                   onUpdate() { ripple.opacity = 255 * Math.sqrt(ripple._opacity); },
                                   onComplete() { ripple.visible = false; } });
    }

    _rippleAnimation() {
        // Show three concentric ripples expanding outwards; the exact
        // parameters were found by trial and error, so don't look
        // for them to make perfect sense mathematically

        //                              delay  time  scale opacity => scale
        this._animRipple(this._ripple1, 0.0,   0.83,  0.25,  1.0,     1.5);
        this._animRipple(this._ripple2, 0.05,  1.0,   0.0,   0.7,     1.25);
        this._animRipple(this._ripple3, 0.35,  1.0,   0.0,   0.3,     1);
    }

    _toggleOverview() {
        if (this._monitor.inFullscreen && !Main.overview.visible)
            return;

        if (Main.overview.shouldToggleByCornerOrButton()) {
            this._rippleAnimation();
            Main.overview.toggle();
        }
    }

    handleDragOver(source, actor, x, y, time) {
        if (source != Main.xdndHandler)
            return DND.DragMotionResult.CONTINUE;

        this._toggleOverview();

        return DND.DragMotionResult.CONTINUE;
    }

    _onCornerEntered() {
        if (!this._entered) {
            this._entered = true;
            this._toggleOverview();
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onCornerLeft(actor, event) {
        if (event.get_related() != this.actor)
            this._entered = false;
        // Consume event, otherwise this will confuse onEnvironsLeft
        return Clutter.EVENT_STOP;
    }

    _onEnvironsLeft(actor, event) {
        if (event.get_related() != this._corner)
            this._entered = false;
        return Clutter.EVENT_PROPAGATE;
    }
};

var PressureBarrier = class PressureBarrier {
    constructor(threshold, timeout, actionMode) {
        this._threshold = threshold;
        this._timeout = timeout;
        this._actionMode = actionMode;
        this._barriers = [];
        this._eventFilter = null;

        this._isTriggered = false;
        this._reset();
    }

    addBarrier(barrier) {
        barrier._pressureHitId = barrier.connect('hit', this._onBarrierHit.bind(this));
        barrier._pressureLeftId = barrier.connect('left', this._onBarrierLeft.bind(this));

        this._barriers.push(barrier);
    }

    _disconnectBarrier(barrier) {
        barrier.disconnect(barrier._pressureHitId);
        barrier.disconnect(barrier._pressureLeftId);
    }

    removeBarrier(barrier) {
        this._disconnectBarrier(barrier);
        this._barriers.splice(this._barriers.indexOf(barrier), 1);
    }

    destroy() {
        this._barriers.forEach(this._disconnectBarrier.bind(this));
        this._barriers = [];
    }

    setEventFilter(filter) {
        this._eventFilter = filter;
    }

    _reset() {
        this._barrierEvents = [];
        this._currentPressure = 0;
        this._lastTime = 0;
    }

    _isHorizontal(barrier) {
        return barrier.y1 == barrier.y2;
    }

    _getDistanceAcrossBarrier(barrier, event) {
        if (this._isHorizontal(barrier))
            return Math.abs(event.dy);
        else
            return Math.abs(event.dx);
    }

    _getDistanceAlongBarrier(barrier, event) {
        if (this._isHorizontal(barrier))
            return Math.abs(event.dx);
        else
            return Math.abs(event.dy);
    }

    _trimBarrierEvents() {
        // Events are guaranteed to be sorted in time order from
        // oldest to newest, so just look for the first old event,
        // and then chop events after that off.
        let i = 0;
        let threshold = this._lastTime - this._timeout;

        while (i < this._barrierEvents.length) {
            let [time, distance] = this._barrierEvents[i];
            if (time >= threshold)
                break;
            i++;
        }

        let firstNewEvent = i;

        for (i = 0; i < firstNewEvent; i++) {
            let [time, distance] = this._barrierEvents[i];
            this._currentPressure -= distance;
        }

        this._barrierEvents = this._barrierEvents.slice(firstNewEvent);
    }

    _onBarrierLeft(barrier, event) {
        barrier._isHit = false;
        if (this._barriers.every(b => !b._isHit)) {
            this._reset();
            this._isTriggered = false;
        }
    }

    _trigger() {
        this._isTriggered = true;
        this.emit('trigger');
        this._reset();
    }

    _onBarrierHit(barrier, event) {
        barrier._isHit = true;

        // If we've triggered the barrier, wait until the pointer has the
        // left the barrier hitbox until we trigger it again.
        if (this._isTriggered)
            return;

        if (this._eventFilter && this._eventFilter(event))
            return;

        // Throw out all events not in the proper keybinding mode
        if (!(this._actionMode & Main.actionMode))
            return;

        let slide = this._getDistanceAlongBarrier(barrier, event);
        let distance = this._getDistanceAcrossBarrier(barrier, event);

        if (distance >= this._threshold) {
            this._trigger();
            return;
        }

        // Throw out events where the cursor is move more
        // along the axis of the barrier than moving with
        // the barrier.
        if (slide > distance)
            return;

        this._lastTime = event.time;

        this._trimBarrierEvents();
        distance = Math.min(15, distance);

        this._barrierEvents.push([event.time, distance]);
        this._currentPressure += distance;

        if (this._currentPressure >= this._threshold)
            this._trigger();
    }
};
Signals.addSignalMethods(PressureBarrier.prototype);
(uuay)mpris.jsIconst { Gio, Shell, St } = imports.gi;
const Signals = imports.signals;

const Calendar = imports.ui.calendar;
const Main = imports.ui.main;
const MessageList = imports.ui.messageList;

const { loadInterfaceXML } = imports.misc.fileUtils;

const DBusIface = loadInterfaceXML('org.freedesktop.DBus');
const DBusProxy = Gio.DBusProxy.makeProxyWrapper(DBusIface);

const MprisIface = loadInterfaceXML('org.mpris.MediaPlayer2');
const MprisProxy = Gio.DBusProxy.makeProxyWrapper(MprisIface);

const MprisPlayerIface = loadInterfaceXML('org.mpris.MediaPlayer2.Player');
const MprisPlayerProxy = Gio.DBusProxy.makeProxyWrapper(MprisPlayerIface);

const MPRIS_PLAYER_PREFIX = 'org.mpris.MediaPlayer2.';

var MediaMessage = class MediaMessage extends MessageList.Message {
    constructor(player) {
        super('', '');

        this._player = player;

        this._icon = new St.Icon({ style_class: 'media-message-cover-icon' });
        this.setIcon(this._icon);

        this._prevButton = this.addMediaControl('media-skip-backward-symbolic',
            () => {
                this._player.previous();
            });

        this._playPauseButton = this.addMediaControl(null,
            () => {
                this._player.playPause();
            });

        this._nextButton = this.addMediaControl('media-skip-forward-symbolic',
            () => {
                this._player.next();
            });

        this._player.connect('changed', this._update.bind(this));
        this._player.connect('closed', this.close.bind(this));
        this._update();
    }

    _onClicked() {
        this._player.raise();
        Main.panel.closeCalendar();
    }

    _updateNavButton(button, sensitive) {
        button.reactive = sensitive;
    }

    _update() {
        this.setTitle(this._player.trackArtists.join(', '));
        this.setBody(this._player.trackTitle);

        if (this._player.trackCoverUrl) {
            let file = Gio.File.new_for_uri(this._player.trackCoverUrl);
            this._icon.gicon = new Gio.FileIcon({ file: file });
            this._icon.remove_style_class_name('fallback');
        } else {
            this._icon.icon_name = 'audio-x-generic-symbolic';
            this._icon.add_style_class_name('fallback');
        }

        let isPlaying = this._player.status == 'Playing';
        let iconName = isPlaying ? 'media-playback-pause-symbolic'
                                 : 'media-playback-start-symbolic';
        this._playPauseButton.child.icon_name = iconName;

        this._updateNavButton(this._prevButton, this._player.canGoPrevious);
        this._updateNavButton(this._nextButton, this._player.canGoNext);
    }
};

var MprisPlayer = class MprisPlayer {
    constructor(busName) {
        this._mprisProxy = new MprisProxy(Gio.DBus.session, busName,
                                          '/org/mpris/MediaPlayer2',
                                          this._onMprisProxyReady.bind(this));
        this._playerProxy = new MprisPlayerProxy(Gio.DBus.session, busName,
                                                 '/org/mpris/MediaPlayer2',
                                                 this._onPlayerProxyReady.bind(this));

        this._visible = false;
        this._trackArtists = [];
        this._trackTitle = '';
        this._trackCoverUrl = '';
    }

    get status() {
        return this._playerProxy.PlaybackStatus;
    }

    get trackArtists() {
        return this._trackArtists;
    }

    get trackTitle() {
        return this._trackTitle;
    }

    get trackCoverUrl() {
        return this._trackCoverUrl;
    }

    playPause() {
        this._playerProxy.PlayPauseRemote();
    }

    get canGoNext() {
        return this._playerProxy.CanGoNext;
    }

    next() {
        this._playerProxy.NextRemote();
    }

    get canGoPrevious() {
        return this._playerProxy.CanGoPrevious;
    }

    previous() {
        this._playerProxy.PreviousRemote();
    }

    raise() {
        // The remote Raise() method may run into focus stealing prevention,
        // so prefer activating the app via .desktop file if possible
        let app = null;
        if (this._mprisProxy.DesktopEntry) {
            let desktopId = this._mprisProxy.DesktopEntry + '.desktop';
            app = Shell.AppSystem.get_default().lookup_app(desktopId);
        }

        if (app)
            app.activate();
        else if (this._mprisProxy.CanRaise)
            this._mprisProxy.RaiseRemote();
    }

    _close() {
        this._mprisProxy.disconnect(this._ownerNotifyId);
        this._mprisProxy = null;

        this._playerProxy.disconnect(this._propsChangedId);
        this._playerProxy = null;

        this.emit('closed');
    }

    _onMprisProxyReady() {
        this._ownerNotifyId = this._mprisProxy.connect('notify::g-name-owner',
            () => {
                if (!this._mprisProxy.g_name_owner)
                    this._close();
            });
    }

    _onPlayerProxyReady() {
        this._propsChangedId = this._playerProxy.connect('g-properties-changed',
                                                         this._updateState.bind(this));
        this._updateState();
    }

    _updateState() {
        let metadata = {};
        for (let prop in this._playerProxy.Metadata)
            metadata[prop] = this._playerProxy.Metadata[prop].deep_unpack();

        this._trackArtists = metadata['xesam:artist'] || [_("Unknown artist")];
        this._trackTitle = metadata['xesam:title'] || _("Unknown title");
        this._trackCoverUrl = metadata['mpris:artUrl'] || '';
        this.emit('changed');

        let visible = this._playerProxy.CanPlay;

        if (this._visible != visible) {
            this._visible = visible;
            if (visible)
                this.emit('show');
            else
                this._close();
        }
    }
};
Signals.addSignalMethods(MprisPlayer.prototype);

var MediaSection = class MediaSection extends MessageList.MessageListSection {
    constructor() {
        super();

        this._players = new Map();

        this._proxy = new DBusProxy(Gio.DBus.session,
                                    'org.freedesktop.DBus',
                                    '/org/freedesktop/DBus',
                                    this._onProxyReady.bind(this));
    }

    _shouldShow() {
        return !this.empty && Calendar.isToday(this._date);
    }

    _addPlayer(busName) {
        if (this._players.get(busName))
            return;

        let player = new MprisPlayer(busName);
        player.connect('closed',
            () => {
                this._players.delete(busName);
            });
        player.connect('show',
            () => {
                let message = new MediaMessage(player);
                this.addMessage(message, true);
            });
        this._players.set(busName, player);
    }

    _onProxyReady() {
        this._proxy.ListNamesRemote(([names]) => {
            names.forEach(name => {
                if (!name.startsWith(MPRIS_PLAYER_PREFIX))
                    return;

                this._addPlayer(name);
            });
        });
        this._proxy.connectSignal('NameOwnerChanged',
                                  this._onNameOwnerChanged.bind(this));
    }

    _onNameOwnerChanged(proxy, sender, [name, oldOwner, newOwner]) {
        if (!name.startsWith(MPRIS_PLAYER_PREFIX))
            return;

        if (newOwner && !oldOwner)
            this._addPlayer(name);
    }
};
(uuay)automountManager.jsv%// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib } = imports.gi;
const Mainloop = imports.mainloop;
const Params = imports.misc.params;

const GnomeSession = imports.misc.gnomeSession;
const ShellMountOperation = imports.ui.shellMountOperation;

var GNOME_SESSION_AUTOMOUNT_INHIBIT = 16;

// GSettings keys
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
const SETTING_ENABLE_AUTOMOUNT = 'automount';

var AUTORUN_EXPIRE_TIMEOUT_SECS = 10;

var AutomountManager = class {
    constructor() {
        this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
        this._volumeQueue = [];
        this._activeOperations = new Map();
        this._session = new GnomeSession.SessionManager();
        this._session.connectSignal('InhibitorAdded',
                                    this._InhibitorsChanged.bind(this));
        this._session.connectSignal('InhibitorRemoved',
                                    this._InhibitorsChanged.bind(this));
        this._inhibited = false;

        this._volumeMonitor = Gio.VolumeMonitor.get();
    }

    enable() {
        this._volumeAddedId = this._volumeMonitor.connect('volume-added', this._onVolumeAdded.bind(this));
        this._volumeRemovedId = this._volumeMonitor.connect('volume-removed', this._onVolumeRemoved.bind(this));
        this._driveConnectedId = this._volumeMonitor.connect('drive-connected', this._onDriveConnected.bind(this));
        this._driveDisconnectedId = this._volumeMonitor.connect('drive-disconnected', this._onDriveDisconnected.bind(this));
        this._driveEjectButtonId = this._volumeMonitor.connect('drive-eject-button', this._onDriveEjectButton.bind(this));

        this._mountAllId = Mainloop.idle_add(this._startupMountAll.bind(this));
        GLib.Source.set_name_by_id(this._mountAllId, '[gnome-shell] this._startupMountAll');
    }

    disable() {
        this._volumeMonitor.disconnect(this._volumeAddedId);
        this._volumeMonitor.disconnect(this._volumeRemovedId);
        this._volumeMonitor.disconnect(this._driveConnectedId);
        this._volumeMonitor.disconnect(this._driveDisconnectedId);
        this._volumeMonitor.disconnect(this._driveEjectButtonId);

        if (this._mountAllId > 0) {
            Mainloop.source_remove(this._mountAllId);
            this._mountAllId = 0;
        }
    }

    _InhibitorsChanged(object, senderName, [inhibtor]) {
        this._session.IsInhibitedRemote(GNOME_SESSION_AUTOMOUNT_INHIBIT,
            (result, error) => {
                if (!error) {
                    this._inhibited = result[0];
                }
            });
    }

    _startupMountAll() {
        let volumes = this._volumeMonitor.get_volumes();
        volumes.forEach(volume => {
            this._checkAndMountVolume(volume, { checkSession: false,
                                                useMountOp: false,
                                                allowAutorun: false });
        });

        this._mountAllId = 0;
        return GLib.SOURCE_REMOVE;
    }

    _onDriveConnected() {
        // if we're not in the current ConsoleKit session,
        // or screensaver is active, don't play sounds
        if (!this._session.SessionIsActive)
            return;

        let player = global.display.get_sound_player();
        player.play_from_theme('device-added-media',
                               _("External drive connected"),
                               null);
    }

    _onDriveDisconnected() {
        // if we're not in the current ConsoleKit session,
        // or screensaver is active, don't play sounds
        if (!this._session.SessionIsActive)
            return;

        let player = global.display.get_sound_player();
        player.play_from_theme('device-removed-media',
                               _("External drive disconnected"),
                               null);
    }

    _onDriveEjectButton(monitor, drive) {
        // TODO: this code path is not tested, as the GVfs volume monitor
        // doesn't emit this signal just yet.
        if (!this._session.SessionIsActive)
            return;

        // we force stop/eject in this case, so we don't have to pass a
        // mount operation object
        if (drive.can_stop()) {
            drive.stop
                (Gio.MountUnmountFlags.FORCE, null, null,
                 (drive, res) => {
                     try {
                         drive.stop_finish(res);
                     } catch (e) {
                         log("Unable to stop the drive after drive-eject-button " + e.toString());
                     }
                 });
        } else if (drive.can_eject()) {
            drive.eject_with_operation 
                (Gio.MountUnmountFlags.FORCE, null, null,
                 (drive, res) => {
                     try {
                         drive.eject_with_operation_finish(res);
                     } catch (e) {
                         log("Unable to eject the drive after drive-eject-button " + e.toString());
                     }
                 });
        }
    }

    _onVolumeAdded(monitor, volume) {
        this._checkAndMountVolume(volume);
    }

    _checkAndMountVolume(volume, params) {
        params = Params.parse(params, { checkSession: true,
                                        useMountOp: true,
                                        allowAutorun: true });

        if (params.checkSession) {
            // if we're not in the current ConsoleKit session,
            // don't attempt automount
            if (!this._session.SessionIsActive)
                return;
        }

        if (this._inhibited)
            return;

        // Volume is already mounted, don't bother.
        if (volume.get_mount())
            return;

        if (!this._settings.get_boolean(SETTING_ENABLE_AUTOMOUNT) ||
            !volume.should_automount() ||
            !volume.can_mount()) {
            // allow the autorun to run anyway; this can happen if the
            // mount gets added programmatically later, even if 
            // should_automount() or can_mount() are false, like for
            // blank optical media.
            this._allowAutorun(volume);
            this._allowAutorunExpire(volume);

            return;
        }

        if (params.useMountOp) {
            let operation = new ShellMountOperation.ShellMountOperation(volume);
            this._mountVolume(volume, operation, params.allowAutorun);
        } else {
            this._mountVolume(volume, null, params.allowAutorun);
        }
    }

    _mountVolume(volume, operation, allowAutorun) {
        if (allowAutorun)
            this._allowAutorun(volume);

        let mountOp = operation ? operation.mountOp : null;
        this._activeOperations.set(volume, operation);

        volume.mount(0, mountOp, null,
                     this._onVolumeMounted.bind(this));
    }

    _onVolumeMounted(volume, res) {
        this._allowAutorunExpire(volume);

        try {
            volume.mount_finish(res);
            this._closeOperation(volume);
        } catch (e) {
            // FIXME: we will always get G_IO_ERROR_FAILED from the gvfs udisks
            // backend, see https://bugs.freedesktop.org/show_bug.cgi?id=51271
            // To reask the password if the user input was empty or wrong, we
            // will check for corresponding error messages. However, these
            // error strings are not unique for the cases in the comments below.
            if (e.message.includes('No key available with this passphrase') || // cryptsetup
                e.message.includes('No key available to unlock device') ||     // udisks (no password)
                e.message.includes('Error unlocking')) {                       // udisks (wrong password)
                this._reaskPassword(volume);
            } else {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED_HANDLED))
                    log('Unable to mount volume ' + volume.get_name() + ': ' + e.toString());

                this._closeOperation(volume);
            }
        }
    }

    _onVolumeRemoved(monitor, volume) {
        if (volume._allowAutorunExpireId && volume._allowAutorunExpireId > 0) {
            Mainloop.source_remove(volume._allowAutorunExpireId);
            delete volume._allowAutorunExpireId;
        }
        this._volumeQueue = 
            this._volumeQueue.filter(element => (element != volume));
    }

    _reaskPassword(volume) {
        let prevOperation = this._activeOperations.get(volume);
        let existingDialog = prevOperation ? prevOperation.borrowDialog() : null;
        let operation = 
            new ShellMountOperation.ShellMountOperation(volume,
                                                        { existingDialog: existingDialog });
        this._mountVolume(volume, operation);
    }

    _closeOperation(volume) {
        let operation = this._activeOperations.get(volume);
        if (!operation)
            return;
        operation.close();
        this._activeOperations.delete(volume);
    }

    _allowAutorun(volume) {
        volume.allowAutorun = true;
    }

    _allowAutorunExpire(volume) {
        let id = Mainloop.timeout_add_seconds(AUTORUN_EXPIRE_TIMEOUT_SECS, () => {
            volume.allowAutorun = false;
            delete volume._allowAutorunExpireId;
            return GLib.SOURCE_REMOVE;
        });
        volume._allowAutorunExpireId = id;
        GLib.Source.set_name_by_id(id, '[gnome-shell] volume.allowAutorun');
    }
};
var Component = AutomountManager;
(uuay)gnomeSession.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;

const { loadInterfaceXML } = imports.misc.fileUtils;

const PresenceIface = loadInterfaceXML('org.gnome.SessionManager.Presence');

var PresenceStatus = {
    AVAILABLE: 0,
    INVISIBLE: 1,
    BUSY: 2,
    IDLE: 3
};

var PresenceProxy = Gio.DBusProxy.makeProxyWrapper(PresenceIface);
function Presence(initCallback, cancellable) {
    return new PresenceProxy(Gio.DBus.session, 'org.gnome.SessionManager',
                             '/org/gnome/SessionManager/Presence', initCallback, cancellable);
}

// Note inhibitors are immutable objects, so they don't
// change at runtime (changes always come in the form
// of new inhibitors)
const InhibitorIface = loadInterfaceXML('org.gnome.SessionManager.Inhibitor');
var InhibitorProxy = Gio.DBusProxy.makeProxyWrapper(InhibitorIface);
function Inhibitor(objectPath, initCallback, cancellable) {
    return new InhibitorProxy(Gio.DBus.session, 'org.gnome.SessionManager', objectPath, initCallback, cancellable);
}

// Not the full interface, only the methods we use
const SessionManagerIface = loadInterfaceXML('org.gnome.SessionManager');
var SessionManagerProxy = Gio.DBusProxy.makeProxyWrapper(SessionManagerIface);
function SessionManager(initCallback, cancellable) {
    return new SessionManagerProxy(Gio.DBus.session, 'org.gnome.SessionManager', '/org/gnome/SessionManager', initCallback, cancellable);
}
(uuay)modemManager.js$// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, NMA } = imports.gi;
const Signals = imports.signals;

const { loadInterfaceXML } = imports.misc.fileUtils;

// _getMobileProvidersDatabase:
//
// Gets the database of mobile providers, with references between MCCMNC/SID and
// operator name
//
let _mpd;
function _getMobileProvidersDatabase() {
    if (_mpd == null) {
        try {
            _mpd = new NMA.MobileProvidersDatabase();
            _mpd.init(null);
        } catch (e) {
            log(e.message);
            _mpd = null;
        }
    }

    return _mpd;
}

// _findProviderForMccMnc:
// @operator_name: operator name
// @operator_code: operator code
//
// Given an operator name string (which may not be a real operator name) and an
// operator code string, tries to find a proper operator name to display.
//
function _findProviderForMccMnc(operator_name, operator_code) {
    if (operator_name) {
        if (operator_name.length != 0 &&
            (operator_name.length > 6 || operator_name.length < 5)) {
            // this looks like a valid name, i.e. not an MCCMNC (that some
            // devices return when not yet connected
            return operator_name;
        }

        if (isNaN(parseInt(operator_name))) {
            // name is definitely not a MCCMNC, so it may be a name
            // after all; return that
            return operator_name;
        }
    }

    let needle;
    if ((!operator_name || operator_name.length == 0) && operator_code)
        needle = operator_code;
    else if (operator_name && (operator_name.length == 6 || operator_name.length == 5))
        needle = operator_name;
    else // nothing to search
        return null;

    let mpd = _getMobileProvidersDatabase();
    if (mpd) {
        let provider = mpd.lookup_3gpp_mcc_mnc(needle);
        if (provider)
            return provider.get_name();
    }
    return null;
}

// _findProviderForSid:
// @sid: System Identifier of the serving CDMA network
//
// Tries to find the operator name corresponding to the given SID
//
function _findProviderForSid(sid) {
    if (sid == 0)
        return null;

    let mpd = _getMobileProvidersDatabase();
    if (mpd) {
        let provider = mpd.lookup_cdma_sid(sid);
        if (provider)
            return provider.get_name();
    }
    return null;
}


//------------------------------------------------------------------------------
// Support for the old ModemManager interface (MM < 0.7)
//------------------------------------------------------------------------------


// The following are not the complete interfaces, just the methods we need
// (or may need in the future)

const ModemGsmNetworkInterface = loadInterfaceXML('org.freedesktop.ModemManager.Modem.Gsm.Network');
const ModemGsmNetworkProxy = Gio.DBusProxy.makeProxyWrapper(ModemGsmNetworkInterface);

const ModemCdmaInterface = loadInterfaceXML('org.freedesktop.ModemManager.Modem.Cdma');
const ModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(ModemCdmaInterface);

var ModemGsm = class {
    constructor(path) {
        this._proxy = new ModemGsmNetworkProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);

        this.signal_quality = 0;
        this.operator_name = null;

        // Code is duplicated because the function have different signatures
        this._proxy.connectSignal('SignalQuality', (proxy, sender, [quality]) => {
            this.signal_quality = quality;
            this.emit('notify::signal-quality');
        });
        this._proxy.connectSignal('RegistrationInfo', (proxy, sender, [status, code, name]) => {
            this.operator_name = _findProviderForMccMnc(name, code);
            this.emit('notify::operator-name');
        });
        this._proxy.GetRegistrationInfoRemote(([result], err) => {
            if (err) {
                log(err);
                return;
            }

            let [status, code, name] = result;
            this.operator_name = _findProviderForMccMnc(name, code);
            this.emit('notify::operator-name');
        });
        this._proxy.GetSignalQualityRemote((result, err) => {
            if (err) {
                // it will return an error if the device is not connected
                this.signal_quality = 0;
            } else {
                let [quality] = result;
                this.signal_quality = quality;
            }
            this.emit('notify::signal-quality');
        });
    }
};
Signals.addSignalMethods(ModemGsm.prototype);

var ModemCdma = class {
    constructor(path) {
        this._proxy = new ModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);

        this.signal_quality = 0;
        this.operator_name = null;
        this._proxy.connectSignal('SignalQuality', (proxy, sender, params) => {
            this.signal_quality = params[0];
            this.emit('notify::signal-quality');

            // receiving this signal means the device got activated
            // and we can finally call GetServingSystem
            if (this.operator_name == null)
                this._refreshServingSystem();
        });
        this._proxy.GetSignalQualityRemote((result, err) => {
            if (err) {
                // it will return an error if the device is not connected
                this.signal_quality = 0;
            } else {
                let [quality] = result;
                this.signal_quality = quality;
            }
            this.emit('notify::signal-quality');
        });
    }

    _refreshServingSystem() {
        this._proxy.GetServingSystemRemote(([result], err) => {
            if (err) {
                // it will return an error if the device is not connected
                this.operator_name = null;
            } else {
                let [bandClass, band, sid] = result;

                this.operator_name = _findProviderForSid(sid)
            }
            this.emit('notify::operator-name');
        });
    }
};
Signals.addSignalMethods(ModemCdma.prototype);


//------------------------------------------------------------------------------
// Support for the new ModemManager1 interface (MM >= 0.7)
//------------------------------------------------------------------------------

const BroadbandModemInterface = loadInterfaceXML('org.freedesktop.ModemManager1.Modem');
const BroadbandModemProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemInterface);

const BroadbandModem3gppInterface = loadInterfaceXML('org.freedesktop.ModemManager1.Modem.Modem3gpp');
const BroadbandModem3gppProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModem3gppInterface);

const BroadbandModemCdmaInterface = loadInterfaceXML('org.freedesktop.ModemManager1.Modem.ModemCdma');
const BroadbandModemCdmaProxy = Gio.DBusProxy.makeProxyWrapper(BroadbandModemCdmaInterface);

var BroadbandModem = class {
    constructor(path, capabilities) {
        this._proxy = new BroadbandModemProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
        this._proxy_3gpp = new BroadbandModem3gppProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
        this._proxy_cdma = new BroadbandModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager1', path);
        this._capabilities = capabilities;

        this._proxy.connect('g-properties-changed', (proxy, properties) => {
            if ('SignalQuality' in properties.deep_unpack())
                this._reloadSignalQuality();
        });
        this._reloadSignalQuality();

        this._proxy_3gpp.connect('g-properties-changed', (proxy, properties) => {
            let unpacked = properties.deep_unpack();
            if ('OperatorName' in unpacked || 'OperatorCode' in unpacked)
                this._reload3gppOperatorName();
        });
        this._reload3gppOperatorName();

        this._proxy_cdma.connect('g-properties-changed', (proxy, properties) => {
            let unpacked = properties.deep_unpack();
            if ('Nid' in unpacked || 'Sid' in unpacked)
                this._reloadCdmaOperatorName();
        });
        this._reloadCdmaOperatorName();
    }

    _reloadSignalQuality() {
        let [quality, recent] = this._proxy.SignalQuality;
        this.signal_quality = quality;
        this.emit('notify::signal-quality');
    }

    _reloadOperatorName() {
        let new_name = "";
        if (this.operator_name_3gpp && this.operator_name_3gpp.length > 0)
            new_name += this.operator_name_3gpp;

        if (this.operator_name_cdma && this.operator_name_cdma.length > 0) {
            if (new_name != "")
                new_name += ", ";
            new_name += this.operator_name_cdma;
        }

        this.operator_name = new_name;
        this.emit('notify::operator-name');
    }

    _reload3gppOperatorName() {
        let name = this._proxy_3gpp.OperatorName;
        let code = this._proxy_3gpp.OperatorCode;
        this.operator_name_3gpp = _findProviderForMccMnc(name, code);
        this._reloadOperatorName();
    }

    _reloadCdmaOperatorName() {
        let sid = this._proxy_cdma.Sid;
        this.operator_name_cdma = _findProviderForSid(sid);
        this._reloadOperatorName();
    }
};
Signals.addSignalMethods(BroadbandModem.prototype);
(uuay)perf/gGinputMethod.js�%// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const { Clutter, GLib, GObject, IBus } = imports.gi;

const Keyboard = imports.ui.status.keyboard;

var HIDE_PANEL_TIME = 50;

var InputMethod = GObject.registerClass(
class InputMethod extends Clutter.InputMethod {
    _init() {
        super._init();
        this._hints = 0;
        this._purpose = 0;
        this._currentFocus = null;
        this._preeditStr = '';
        this._preeditPos = 0;
        this._preeditVisible = false;
        this._hidePanelId = 0;
        this._ibus = IBus.Bus.new_async();
        this._ibus.connect('connected', this._onConnected.bind(this));
        this._ibus.connect('disconnected', this._clear.bind(this));
        this.connect('notify::can-show-preedit', this._updateCapabilities.bind(this));

        this._inputSourceManager = Keyboard.getInputSourceManager();
        this._sourceChangedId = this._inputSourceManager.connect('current-source-changed',
                                                                 this._onSourceChanged.bind(this));
        this._currentSource = this._inputSourceManager.currentSource;

        if (this._ibus.is_connected())
            this._onConnected();
    }

    get currentFocus() {
        return this._currentFocus;
    }

    _updateCapabilities() {
        let caps = 0;

        if (this.can_show_preedit)
            caps |= IBus.Capabilite.PREEDIT_TEXT;

        if (this._currentFocus)
            caps |= IBus.Capabilite.FOCUS | IBus.Capabilite.SURROUNDING_TEXT;
        else
            caps |= IBus.Capabilite.PREEDIT_TEXT | IBus.Capabilite.AUXILIARY_TEXT | IBus.Capabilite.LOOKUP_TABLE | IBus.Capabilite.PROPERTY;

        if (this._context)
            this._context.set_capabilities(caps);
    }

    _onSourceChanged() {
        this._currentSource = this._inputSourceManager.currentSource;
    }

    _onConnected() {
        this._ibus.create_input_context_async ('gnome-shell', -1, null,
                                               this._setContext.bind(this));
    }

    _setContext(bus, res) {
        this._context = this._ibus.create_input_context_async_finish(res);
        this._context.connect('commit-text', this._onCommitText.bind(this));
        this._context.connect('delete-surrounding-text', this._onDeleteSurroundingText.bind(this));
        this._context.connect('update-preedit-text', this._onUpdatePreeditText.bind(this));
        this._context.connect('show-preedit-text', this._onShowPreeditText.bind(this));
        this._context.connect('hide-preedit-text', this._onHidePreeditText.bind(this));
        this._context.connect('forward-key-event', this._onForwardKeyEvent.bind(this));

        this._updateCapabilities();
    }

    _clear() {
        this._context = null;
        this._hints = 0;
        this._purpose = 0;
        this._preeditStr = ''
        this._preeditPos = 0;
        this._preeditVisible = false;
    }

    _emitRequestSurrounding() {
        if (this._context.needs_surrounding_text())
            this.emit('request-surrounding');
    }

    _onCommitText(context, text) {
        this.commit(text.get_text());
    }

    _onDeleteSurroundingText(context) {
        this.delete_surrounding();
    }

    _onUpdatePreeditText(context, text, pos, visible) {
        if (text == null)
            return;

        let preedit = text.get_text();

        if (visible)
            this.set_preedit_text(preedit, pos);
        else if (this._preeditVisible)
            this.set_preedit_text(null, pos);

        this._preeditStr = preedit;
        this._preeditPos = pos;
        this._preeditVisible = visible;
    }

    _onShowPreeditText(context) {
        this._preeditVisible = true;
        this.set_preedit_text(this._preeditStr, this._preeditPos);
    }

    _onHidePreeditText(context) {
        this.set_preedit_text(null, this._preeditPos);
        this._preeditVisible = false;
    }

    _onForwardKeyEvent(context, keyval, keycode, state) {
        let press = (state & IBus.ModifierType.RELEASE_MASK) == 0;
        state &= ~(IBus.ModifierType.RELEASE_MASK);

        let curEvent = Clutter.get_current_event();
        let time;
        if (curEvent)
            time = curEvent.get_time();
        else
            time = global.display.get_current_time_roundtrip();

        this.forward_key(keyval, keycode + 8, state & Clutter.ModifierType.MODIFIER_MASK, time, press);
    }

    vfunc_focus_in(focus) {
        this._currentFocus = focus;
        if (this._context) {
            this._context.focus_in();
            this._updateCapabilities();
            this._emitRequestSurrounding();
        }

        if (this._hidePanelId) {
            GLib.source_remove(this._hidePanelId);
            this._hidePanelId = 0;
        }
    }

    vfunc_focus_out() {
        this._currentFocus = null;
        if (this._context) {
            this._context.focus_out();
            this._updateCapabilities();
        }

        if (this._preeditStr) {
            // Unset any preedit text
            this.set_preedit_text(null, 0);
            this._preeditStr = null;
        }

        this._hidePanelId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, HIDE_PANEL_TIME, () => {
            this.set_input_panel_state(Clutter.InputPanelState.OFF);
            this._hidePanelId = 0;
            return GLib.SOURCE_REMOVE;
        });
    }

    vfunc_reset() {
        if (this._context) {
            this._context.reset();
            this._emitRequestSurrounding();
        }

        if (this._preeditStr) {
            // Unset any preedit text
            this.set_preedit_text(null, 0);
            this._preeditStr = null;
        }
    }

    vfunc_set_cursor_location(rect) {
        if (this._context) {
            this._context.set_cursor_location(rect.get_x(), rect.get_y(),
                                              rect.get_width(), rect.get_height());
            this._emitRequestSurrounding();
        }
    }

    vfunc_set_surrounding(text, cursor, anchor) {
        if (!this._context || !text)
            return;

        let ibusText = IBus.Text.new_from_string(text);
        this._context.set_surrounding_text(ibusText, cursor, anchor);
    }

    vfunc_update_content_hints(hints) {
        let ibusHints = 0;
        if (hints & Clutter.InputContentHintFlags.COMPLETION)
            ibusHints |= IBus.InputHints.WORD_COMPLETION;
        if (hints & Clutter.InputContentHintFlags.SPELLCHECK)
            ibusHints |= IBus.InputHints.SPELLCHECK;
        if (hints & Clutter.InputContentHintFlags.AUTO_CAPITALIZATION)
            ibusHints |= IBus.InputHints.UPPERCASE_SENTENCES;
        if (hints & Clutter.InputContentHintFlags.LOWERCASE)
            ibusHints |= IBus.InputHints.LOWERCASE;
        if (hints & Clutter.InputContentHintFlags.UPPERCASE)
            ibusHints |= IBus.InputHints.UPPERCASE_CHARS;
        if (hints & Clutter.InputContentHintFlags.TITLECASE)
            ibusHints |= IBus.InputHints.UPPERCASE_WORDS;

        this._hints = ibusHints;
        if (this._context)
            this._context.set_content_type(this._purpose, this._hints);
    }

    vfunc_update_content_purpose(purpose) {
        let ibusPurpose = 0;
        if (purpose == Clutter.InputContentPurpose.NORMAL)
            ibusPurpose = IBus.InputPurpose.FREE_FORM;
        else if (purpose == Clutter.InputContentPurpose.ALPHA)
            ibusPurpose = IBus.InputPurpose.ALPHA;
        else if (purpose == Clutter.InputContentPurpose.DIGITS)
            ibusPurpose = IBus.InputPurpose.DIGITS;
        else if (purpose == Clutter.InputContentPurpose.NUMBER)
            ibusPurpose = IBus.InputPurpose.NUMBER;
        else if (purpose == Clutter.InputContentPurpose.PHONE)
            ibusPurpose = IBus.InputPurpose.PHONE;
        else if (purpose == Clutter.InputContentPurpose.URL)
            ibusPurpose = IBus.InputPurpose.URL;
        else if (purpose == Clutter.InputContentPurpose.EMAIL)
            ibusPurpose = IBus.InputPurpose.EMAIL;
        else if (purpose == Clutter.InputContentPurpose.NAME)
            ibusPurpose = IBus.InputPurpose.NAME;
        else if (purpose == Clutter.InputContentPurpose.PASSWORD)
            ibusPurpose = IBus.InputPurpose.PASSWORD;

        this._purpose = ibusPurpose;
        if (this._context)
            this._context.set_content_type(this._purpose, this._hints);
    }

    vfunc_filter_key_event(event) {
        if (!this._context)
            return false;
        if (!this._currentSource)
            return false;

        let state = event.get_state();
        if (state & IBus.ModifierType.IGNORED_MASK)
            return false;

        if (event.type() == Clutter.EventType.KEY_RELEASE)
            state |= IBus.ModifierType.RELEASE_MASK;

        this._context.process_key_event_async(event.get_key_symbol(),
                                              event.get_key_code() - 8, // Convert XKB keycodes to evcodes
                                              state, -1, null,
                                              (context, res) => {
                                                  try {
                                                      let retval = context.process_key_event_async_finish(res);
                                                      this.notify_key_event(event, retval);
                                                  } catch (e) {
                                                      log('Error processing key on IM: ' + e.message);
                                                  }
                                              });
        return true;
    }
});
(uuay)overview.jsnS// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const Background = imports.ui.background;
const DND = imports.ui.dnd;
const LayoutManager = imports.ui.layout;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const OverviewControls = imports.ui.overviewControls;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;

// Time for initial animation going into Overview mode
var ANIMATION_TIME = 0.25;

// Must be less than ANIMATION_TIME, since we switch to
// or from the overview completely after ANIMATION_TIME,
// and don't want the shading animation to get cut off
var SHADE_ANIMATION_TIME = .20;

var DND_WINDOW_SWITCH_TIMEOUT = 750;

var OVERVIEW_ACTIVATION_TIMEOUT = 0.5;

var ShellInfo = class {
    constructor() {
        this._source = null;
        this._undoCallback = null;
    }

    _onUndoClicked() {
        if (this._undoCallback)
            this._undoCallback();
        this._undoCallback = null;

        if (this._source)
            this._source.destroy();
    }

    setMessage(text, options) {
        options = Params.parse(options, { undoCallback: null,
                                          forFeedback: false
                                        });

        let undoCallback = options.undoCallback;
        let forFeedback = options.forFeedback;

        if (this._source == null) {
            this._source = new MessageTray.SystemNotificationSource();
            this._source.connect('destroy', () => {
                this._source = null;
            });
            Main.messageTray.add(this._source);
        }

        let notification = null;
        if (this._source.notifications.length == 0) {
            notification = new MessageTray.Notification(this._source, text, null);
            notification.setTransient(true);
            notification.setForFeedback(forFeedback);
        } else {
            notification = this._source.notifications[0];
            notification.update(text, null, { clear: true });
        }

        this._undoCallback = undoCallback;
        if (undoCallback)
            notification.addAction(_("Undo"), this._onUndoClicked.bind(this));

        this._source.notify(notification);
    }
};

var Overview = class {
    constructor() {
        this._overviewCreated = false;
        this._initCalled = false;
        this._visible = false;

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    _createOverview() {
        if (this._overviewCreated)
            return;

        if (this.isDummy)
            return;

        this._overviewCreated = true;

        this._overview = new St.BoxLayout({ name: 'overview',
                                            /* Translators: This is the main view to select
                                               activities. See also note for "Activities" string. */
                                            accessible_name: _("Overview"),
                                            vertical: true });
        this._overview.add_constraint(new LayoutManager.MonitorConstraint({ primary: true }));
        this._overview._delegate = this;

        // The main Background actors are inside global.window_group which are
        // hidden when displaying the overview, so we create a new
        // one. Instances of this class share a single CoglTexture behind the
        // scenes which allows us to show the background with different
        // rendering options without duplicating the texture data.
        this._backgroundGroup = new Meta.BackgroundGroup({ reactive: true });
        Main.layoutManager.overviewGroup.add_child(this._backgroundGroup);
        this._bgManagers = [];

        this._desktopFade = new St.Widget();
        Main.layoutManager.overviewGroup.add_child(this._desktopFade);

        this._activationTime = 0;

        this.visible = false;           // animating to overview, in overview, animating out
        this._shown = false;            // show() and not hide()
        this._modal = false;            // have a modal grab
        this.animationInProgress = false;
        this.visibleTarget = false;

        // During transitions, we raise this to the top to avoid having the overview
        // area be reactive; it causes too many issues such as double clicks on
        // Dash elements, or mouseover handlers in the workspaces.
        this._coverPane = new Clutter.Actor({ opacity: 0,
                                              reactive: true });
        Main.layoutManager.overviewGroup.add_child(this._coverPane);
        this._coverPane.connect('event', () => Clutter.EVENT_STOP);

        Main.layoutManager.overviewGroup.add_child(this._overview);

        this._coverPane.hide();

        // XDND
        this._dragMonitor = {
            dragMotion: this._onDragMotion.bind(this)
        };


        Main.layoutManager.overviewGroup.connect('scroll-event',
                                                 this._onScrollEvent.bind(this));
        Main.xdndHandler.connect('drag-begin', this._onDragBegin.bind(this));
        Main.xdndHandler.connect('drag-end', this._onDragEnd.bind(this));

        global.display.connect('restacked', this._onRestacked.bind(this));

        this._windowSwitchTimeoutId = 0;
        this._windowSwitchTimestamp = 0;
        this._lastActiveWorkspaceIndex = -1;
        this._lastHoveredWindow = null;

        if (this._initCalled)
            this.init();
    }

    _updateBackgrounds() {
        for (let i = 0; i < this._bgManagers.length; i++)
            this._bgManagers[i].destroy();

        this._bgManagers = [];

        for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
            let bgManager = new Background.BackgroundManager({ container: this._backgroundGroup,
                                                               monitorIndex: i,
                                                               vignette: true });
            this._bgManagers.push(bgManager);
        }
    }

    _unshadeBackgrounds() {
        let backgrounds = this._backgroundGroup.get_children();
        for (let i = 0; i < backgrounds.length; i++) {
            Tweener.addTween(backgrounds[i],
                             { brightness: 1.0,
                               vignette_sharpness: 0.0,
                               time: SHADE_ANIMATION_TIME,
                               transition: 'easeOutQuad'
                             });
        }
    }

    _shadeBackgrounds() {
        let backgrounds = this._backgroundGroup.get_children();
        for (let i = 0; i < backgrounds.length; i++) {
            Tweener.addTween(backgrounds[i],
                             { brightness: Lightbox.VIGNETTE_BRIGHTNESS,
                               vignette_sharpness: Lightbox.VIGNETTE_SHARPNESS,
                               time: SHADE_ANIMATION_TIME,
                               transition: 'easeOutQuad'
                             });
        }
    }

    _sessionUpdated() {
        const { hasOverview } = Main.sessionMode;
        if (!hasOverview)
            this.hide();

        this.isDummy = !hasOverview;
        this._createOverview();
    }

    // The members we construct that are implemented in JS might
    // want to access the overview as Main.overview to connect
    // signal handlers and so forth. So we create them after
    // construction in this init() method.
    init() {
        this._initCalled = true;

        if (this.isDummy)
            return;

        this._shellInfo = new ShellInfo();

        // Add a clone of the panel to the overview so spacing and such is
        // automatic
        this._panelGhost = new St.Bin({ child: new Clutter.Clone({ source: Main.panel }),
                                        reactive: false,
                                        opacity: 0 });
        this._overview.add_actor(this._panelGhost);

        this._searchEntry = new St.Entry({ style_class: 'search-entry',
                                           /* Translators: this is the text displayed
                                              in the search entry when no search is
                                              active; it should not exceed ~30
                                              characters. */
                                           hint_text: _("Type to search…"),
                                           track_hover: true,
                                           can_focus: true });
        this._searchEntryBin = new St.Bin({ child: this._searchEntry,
                                            x_align: St.Align.MIDDLE });
        this._overview.add_actor(this._searchEntryBin);

        // Create controls
        this._controls = new OverviewControls.ControlsManager(this._searchEntry);
        this._dash = this._controls.dash;
        this.viewSelector = this._controls.viewSelector;

        // Add our same-line elements after the search entry
        this._overview.add(this._controls.actor, { y_fill: true, expand: true });

        // TODO - recalculate everything when desktop size changes
        this.dashIconSize = this._dash.iconSize;
        this._dash.connect('icon-size-changed', () => {
            this.dashIconSize = this._dash.iconSize;
        });

        Main.layoutManager.connect('monitors-changed', this._relayout.bind(this));
        this._relayout();
    }

    addSearchProvider(provider) {
        this.viewSelector.addSearchProvider(provider);
    }

    removeSearchProvider(provider) {
        this.viewSelector.removeSearchProvider(provider);
    }

    //
    // options:
    //  - undoCallback (function): the callback to be called if undo support is needed
    //  - forFeedback (boolean): whether the message is for direct feedback of a user action
    //
    setMessage(text, options) {
        if (this.isDummy)
            return;

        this._shellInfo.setMessage(text, options);
    }

    _onDragBegin() {
        this._inXdndDrag = true;

        DND.addDragMonitor(this._dragMonitor);
        // Remember the workspace we started from
        let workspaceManager = global.workspace_manager;
        this._lastActiveWorkspaceIndex = workspaceManager.get_active_workspace_index();
    }

    _onDragEnd(time) {
        this._inXdndDrag = false;

        // In case the drag was canceled while in the overview
        // we have to go back to where we started and hide
        // the overview
        if (this._shown) {
            let workspaceManager = global.workspace_manager;
            workspaceManager.get_workspace_by_index(this._lastActiveWorkspaceIndex).activate(time);
            this.hide();
        }
        this._resetWindowSwitchTimeout();
        this._lastHoveredWindow = null;
        DND.removeDragMonitor(this._dragMonitor);
        this.endItemDrag();
    }

    _resetWindowSwitchTimeout() {
        if (this._windowSwitchTimeoutId != 0) {
            Mainloop.source_remove(this._windowSwitchTimeoutId);
            this._windowSwitchTimeoutId = 0;
        }
    }

    _onDragMotion(dragEvent) {
        let targetIsWindow = dragEvent.targetActor &&
                             dragEvent.targetActor._delegate &&
                             dragEvent.targetActor._delegate.metaWindow &&
                             !(dragEvent.targetActor._delegate instanceof WorkspaceThumbnail.WindowClone);

        this._windowSwitchTimestamp = global.get_current_time();

        if (targetIsWindow &&
            dragEvent.targetActor._delegate.metaWindow == this._lastHoveredWindow)
            return DND.DragMotionResult.CONTINUE;

        this._lastHoveredWindow = null;

        this._resetWindowSwitchTimeout();

        if (targetIsWindow) {
            this._lastHoveredWindow = dragEvent.targetActor._delegate.metaWindow;
            this._windowSwitchTimeoutId = Mainloop.timeout_add(DND_WINDOW_SWITCH_TIMEOUT,
                () => {
                    this._windowSwitchTimeoutId = 0;
                    Main.activateWindow(dragEvent.targetActor._delegate.metaWindow,
                                        this._windowSwitchTimestamp);
                    this.hide();
                    this._lastHoveredWindow = null;
                    return GLib.SOURCE_REMOVE;
                });
            GLib.Source.set_name_by_id(this._windowSwitchTimeoutId, '[gnome-shell] Main.activateWindow');
        }

        return DND.DragMotionResult.CONTINUE;
    }

    _onScrollEvent(actor, event) {
        this.emit('scroll-event', event);
        return Clutter.EVENT_PROPAGATE;
    }

    addAction(action) {
        if (this.isDummy)
            return;

        this._backgroundGroup.add_action(action);
    }

    _getDesktopClone() {
        let windows = global.get_window_actors().filter(
            w => w.meta_window.get_window_type() == Meta.WindowType.DESKTOP
        );
        if (windows.length == 0)
            return null;

        let window = windows[0];
        let clone = new Clutter.Clone({ source: window,
                                        x: window.x, y: window.y });
        clone.source.connect('destroy', () => {
            clone.destroy();
        });
        return clone;
    }

    _relayout() {
        // To avoid updating the position and size of the workspaces
        // we just hide the overview. The positions will be updated
        // when it is next shown.
        this.hide();

        this._coverPane.set_position(0, 0);
        this._coverPane.set_size(global.screen_width, global.screen_height);

        this._updateBackgrounds();
    }

    _onRestacked() {
        let stack = global.get_window_actors();
        let stackIndices = {};

        for (let i = 0; i < stack.length; i++) {
            // Use the stable sequence for an integer to use as a hash key
            stackIndices[stack[i].get_meta_window().get_stable_sequence()] = i;
        }

        this.emit('windows-restacked', stackIndices);
    }

    beginItemDrag(source) {
        this.emit('item-drag-begin');
        this._inItemDrag = true;
    }

    cancelledItemDrag(source) {
        this.emit('item-drag-cancelled');
    }

    endItemDrag(source) {
        if (!this._inItemDrag)
            return;
        this.emit('item-drag-end');
        this._inItemDrag = false;
    }

    beginWindowDrag(window) {
        this.emit('window-drag-begin', window);
        this._inWindowDrag = true;
    }

    cancelledWindowDrag(window) {
        this.emit('window-drag-cancelled', window);
    }

    endWindowDrag(window) {
        if (!this._inWindowDrag)
            return;
        this.emit('window-drag-end', window);
        this._inWindowDrag = false;
    }

    focusSearch() {
        this.show();
        this._searchEntry.grab_key_focus();
    }

    fadeInDesktop() {
            this._desktopFade.opacity = 0;
            this._desktopFade.show();
            Tweener.addTween(this._desktopFade,
                             { opacity: 255,
                               time: ANIMATION_TIME,
                               transition: 'easeOutQuad' });
    }

    fadeOutDesktop() {
        if (!this._desktopFade.get_n_children()) {
            let clone = this._getDesktopClone();
            if (!clone)
                return;

            this._desktopFade.add_child(clone);
        }

        this._desktopFade.opacity = 255;
        this._desktopFade.show();
        Tweener.addTween(this._desktopFade,
                         { opacity: 0,
                           time: ANIMATION_TIME,
                           transition: 'easeOutQuad'
                         });
    }

    // Checks if the Activities button is currently sensitive to
    // clicks. The first call to this function within the
    // OVERVIEW_ACTIVATION_TIMEOUT time of the hot corner being
    // triggered will return false. This avoids opening and closing
    // the overview if the user both triggered the hot corner and
    // clicked the Activities button.
    shouldToggleByCornerOrButton() {
        if (this.animationInProgress)
            return false;
        if (this._inItemDrag || this._inWindowDrag)
            return false;
        if (this._activationTime == 0 ||
            GLib.get_monotonic_time() / GLib.USEC_PER_SEC - this._activationTime > OVERVIEW_ACTIVATION_TIMEOUT)
            return true;
        return false;
    }

    _syncGrab() {
        // We delay grab changes during animation so that when removing the
        // overview we don't have a problem with the release of a press/release
        // going to an application.
        if (this.animationInProgress)
            return true;

        if (this._shown) {
            let shouldBeModal = !this._inXdndDrag;
            if (shouldBeModal) {
                if (!this._modal) {
                    if (Main.pushModal(this._overview,
                                       { actionMode: Shell.ActionMode.OVERVIEW })) {
                        this._modal = true;
                    } else {
                        this.hide();
                        return false;
                    }
                }
            }
        } else {
            if (this._modal) {
                Main.popModal(this._overview);
                this._modal = false;
            }
        }
        return true;
    }

    // show:
    //
    // Animates the overview visible and grabs mouse and keyboard input
    show() {
        if (this.isDummy)
            return;
        if (this._shown)
            return;
        this._shown = true;

        if (!this._syncGrab())
            return;

        Main.layoutManager.showOverview();
        this._animateVisible();
    }


    _animateVisible() {
        if (this.visible || this.animationInProgress)
            return;

        this.visible = true;
        this.animationInProgress = true;
        this.visibleTarget = true;
        this._activationTime = GLib.get_monotonic_time() / GLib.USEC_PER_SEC;

        Meta.disable_unredirect_for_display(global.display);
        this.viewSelector.show();

        this._overview.opacity = 0;
        Tweener.addTween(this._overview,
                         { opacity: 255,
                           transition: 'easeOutQuad',
                           time: ANIMATION_TIME,
                           onComplete: this._showDone,
                           onCompleteScope: this
                         });
        this._shadeBackgrounds();

        this._coverPane.raise_top();
        this._coverPane.show();
        this.emit('showing');
    }

    _showDone() {
        this.animationInProgress = false;
        this._desktopFade.hide();
        this._coverPane.hide();

        this.emit('shown');
        // Handle any calls to hide* while we were showing
        if (!this._shown)
            this._animateNotVisible();

        this._syncGrab();
        global.sync_pointer();
    }

    // hide:
    //
    // Reverses the effect of show()
    hide() {
        if (this.isDummy)
            return;

        if (!this._shown)
            return;

        let event = Clutter.get_current_event();
        if (event) {
            let type = event.type();
            let button = (type == Clutter.EventType.BUTTON_PRESS ||
                          type == Clutter.EventType.BUTTON_RELEASE);
            let ctrl = (event.get_state() & Clutter.ModifierType.CONTROL_MASK) != 0;
            if (button && ctrl)
                return;
        }

        this._shown = false;

        this._animateNotVisible();
        this._syncGrab();
    }

    _animateNotVisible() {
        if (!this.visible || this.animationInProgress)
            return;

        this.animationInProgress = true;
        this.visibleTarget = false;

        this.viewSelector.animateFromOverview();

        // Make other elements fade out.
        Tweener.addTween(this._overview,
                         { opacity: 0,
                           transition: 'easeOutQuad',
                           time: ANIMATION_TIME,
                           onComplete: this._hideDone,
                           onCompleteScope: this
                         });
        this._unshadeBackgrounds();

        this._coverPane.raise_top();
        this._coverPane.show();
        this.emit('hiding');
    }

    _hideDone() {
        // Re-enable unredirection
        Meta.enable_unredirect_for_display(global.display);

        this.viewSelector.hide();
        this._desktopFade.hide();
        this._coverPane.hide();

        this.visible = false;
        this.animationInProgress = false;

        this.emit('hidden');
        // Handle any calls to show* while we were hiding
        if (this._shown)
            this._animateVisible();
        else
            Main.layoutManager.hideOverview();

        this._syncGrab();
    }

    toggle() {
        if (this.isDummy)
            return;

        if (this.visible)
            this.hide();
        else
            this.show();
    }

    getShowAppsButton() {
        return this._dash.showAppsButton;
    }
};
Signals.addSignalMethods(Overview.prototype);
(uuay)/+screenshot.js�6// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;

const GrabHelper = imports.ui.grabHelper;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

const { loadInterfaceXML } = imports.misc.fileUtils;

const ScreenshotIface = loadInterfaceXML('org.gnome.Shell.Screenshot');

var ScreenshotService = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenshotIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screenshot');

        this._screenShooter = new Map();

        this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });

        Gio.DBus.session.own_name('org.gnome.Shell.Screenshot', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    _createScreenshot(invocation, needsDisk=true) {
        let lockedDown = false;
        if (needsDisk)
            lockedDown = this._lockdownSettings.get_boolean('disable-save-to-disk')

        let sender = invocation.get_sender();
        if (this._screenShooter.has(sender) || lockedDown) {
            invocation.return_value(GLib.Variant.new('(bs)', [false, '']));
            return null;
        }

        let shooter = new Shell.Screenshot();
        shooter._watchNameId =
                        Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
                                           this._onNameVanished.bind(this));

        this._screenShooter.set(sender, shooter);

        return shooter;
    }

    _onNameVanished(connection, name) {
        this._removeShooterForSender(name);
    }

    _removeShooterForSender(sender) {
        let shooter = this._screenShooter.get(sender);
        if (!shooter)
            return;

        Gio.bus_unwatch_name(shooter._watchNameId);
        this._screenShooter.delete(sender);
    }

    _checkArea(x, y, width, height) {
        return x >= 0 && y >= 0 &&
               width > 0 && height > 0 &&
               x + width <= global.screen_width &&
               y + height <= global.screen_height;
    }

    _onScreenshotComplete(result, area, filenameUsed, flash, invocation) {
        if (result) {
            if (flash) {
                let flashspot = new Flashspot(area);
                flashspot.fire(() => {
                    this._removeShooterForSender(invocation.get_sender());
                });
            }
            else
                this._removeShooterForSender(invocation.get_sender());
        }

        let retval = GLib.Variant.new('(bs)', [result, filenameUsed]);
        invocation.return_value(retval);
    }

    _scaleArea(x, y, width, height) {
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        x *= scaleFactor;
        y *= scaleFactor;
        width *= scaleFactor;
        height *= scaleFactor;
        return [x, y, width, height];
    }

    _unscaleArea(x, y, width, height) {
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        x /= scaleFactor;
        y /= scaleFactor;
        width /= scaleFactor;
        height /= scaleFactor;
        return [x, y, width, height];
    }

    ScreenshotAreaAsync(params, invocation) {
        let [x, y, width, height, flash, filename] = params;
        [x, y, width, height] = this._scaleArea(x, y, width, height);
        if (!this._checkArea(x, y, width, height)) {
            invocation.return_error_literal(Gio.IOErrorEnum,
                                            Gio.IOErrorEnum.CANCELLED,
                                            "Invalid params");
            return;
        }
        let screenshot = this._createScreenshot(invocation);
        if (!screenshot)
            return;
        screenshot.screenshot_area (x, y, width, height, filename,
            (o, res) => {
                try {
                    let [result, area, filenameUsed] =
                        screenshot.screenshot_area_finish(res);
                    this._onScreenshotComplete(result, area, filenameUsed,
                                               flash, invocation);
                } catch (e) {
                    invocation.return_gerror (e);
                }
            });
    }

    ScreenshotWindowAsync(params, invocation) {
        let [include_frame, include_cursor, flash, filename] = params;
        let screenshot = this._createScreenshot(invocation);
        if (!screenshot)
            return;
        screenshot.screenshot_window (include_frame, include_cursor, filename,
            (o, res) => {
                try {
                    let [result, area, filenameUsed] =
                        screenshot.screenshot_window_finish(res);
                    this._onScreenshotComplete(result, area, filenameUsed,
                                               flash, invocation);
                } catch (e) {
                    invocation.return_gerror (e);
                }
            });
    }

    ScreenshotAsync(params, invocation) {
        let [include_cursor, flash, filename] = params;
        let screenshot = this._createScreenshot(invocation);
        if (!screenshot)
            return;
        screenshot.screenshot(include_cursor, filename,
            (o, res) => {
                try {
                    let [result, area, filenameUsed] =
                        screenshot.screenshot_finish(res);
                    this._onScreenshotComplete(result, area, filenameUsed,
                                               flash, invocation);
                } catch (e) {
                    invocation.return_gerror (e);
                }
            });
    }

    SelectAreaAsync(params, invocation) {
        let selectArea = new SelectArea();
        selectArea.show();
        selectArea.connect('finished', (selectArea, areaRectangle) => {
            if (areaRectangle) {
                let retRectangle = this._unscaleArea(areaRectangle.x, areaRectangle.y,
                    areaRectangle.width, areaRectangle.height);
                let retval = GLib.Variant.new('(iiii)', retRectangle);
                invocation.return_value(retval);
            } else {
                invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
                    "Operation was cancelled");
            }
        });
    }

    FlashAreaAsync(params, invocation) {
        let [x, y, width, height] = params;
        [x, y, width, height] = this._scaleArea(x, y, width, height);
        if (!this._checkArea(x, y, width, height)) {
            invocation.return_error_literal(Gio.IOErrorEnum,
                                            Gio.IOErrorEnum.CANCELLED,
                                            "Invalid params");
            return;
        }
        let flashspot = new Flashspot({ x : x, y : y, width: width, height: height});
        flashspot.fire();
        invocation.return_value(null);
    }

    PickColorAsync(params, invocation) {
        let pickPixel = new PickPixel();
        pickPixel.show();
        pickPixel.connect('finished', (pickPixel, coords) => {
            if (coords) {
                let screenshot = this._createScreenshot(invocation, false);
                if (!screenshot)
                    return;
                screenshot.pick_color(...coords, (o, res) => {
                    let [success, color] = screenshot.pick_color_finish(res);
                    let { red, green, blue } = color;
                    let retval = GLib.Variant.new('(a{sv})', [{
                        color: GLib.Variant.new('(ddd)', [
                            red / 255.0,
                            green / 255.0,
                            blue / 255.0
                        ])
                    }]);
                    this._removeShooterForSender(invocation.get_sender());
                    invocation.return_value(retval);
                });
            } else {
                invocation.return_error_literal(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
                    "Operation was cancelled");
            }
        });
    }
};

var SelectArea = class {
    constructor() {
        this._startX = -1;
        this._startY = -1;
        this._lastX = 0;
        this._lastY = 0;
        this._result = null;

        this._group = new St.Widget({ visible: false,
                                      reactive: true,
                                      x: 0,
                                      y: 0 });
        Main.uiGroup.add_actor(this._group);

        this._grabHelper = new GrabHelper.GrabHelper(this._group);

        this._group.connect('button-press-event',
                            this._onButtonPress.bind(this));
        this._group.connect('button-release-event',
                            this._onButtonRelease.bind(this));
        this._group.connect('motion-event',
                            this._onMotionEvent.bind(this));

        let constraint = new Clutter.BindConstraint({ source: global.stage,
                                                      coordinate: Clutter.BindCoordinate.ALL });
        this._group.add_constraint(constraint);

        this._rubberband = new St.Widget({
            style_class: 'select-area-rubberband',
            visible: false
        });
        this._group.add_actor(this._rubberband);
    }

    show() {
        if (!this._grabHelper.grab({ actor: this._group,
                                     onUngrab: this._onUngrab.bind(this) }))
            return;

        global.display.set_cursor(Meta.Cursor.CROSSHAIR);
        Main.uiGroup.set_child_above_sibling(this._group, null);
        this._group.visible = true;
    }

    _getGeometry() {
        return { x: Math.min(this._startX, this._lastX),
                 y: Math.min(this._startY, this._lastY),
                 width: Math.abs(this._startX - this._lastX) + 1,
                 height: Math.abs(this._startY - this._lastY) + 1 };
    }

    _onMotionEvent(actor, event) {
        if (this._startX == -1 || this._startY == -1)
            return Clutter.EVENT_PROPAGATE;

        [this._lastX, this._lastY] = event.get_coords();
        this._lastX = Math.floor(this._lastX);
        this._lastY = Math.floor(this._lastY);
        let geometry = this._getGeometry();

        this._rubberband.set_position(geometry.x, geometry.y);
        this._rubberband.set_size(geometry.width, geometry.height);
        this._rubberband.show();

        return Clutter.EVENT_PROPAGATE;
    }

    _onButtonPress(actor, event) {
        [this._startX, this._startY] = event.get_coords();
        this._startX = Math.floor(this._startX);
        this._startY = Math.floor(this._startY);
        this._rubberband.set_position(this._startX, this._startY);

        return Clutter.EVENT_PROPAGATE;
    }

    _onButtonRelease(actor, event) {
        this._result = this._getGeometry();
        Tweener.addTween(this._group,
                         { opacity: 0,
                           time: 0.2,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this._grabHelper.ungrab();
                           }
                         });
        return Clutter.EVENT_PROPAGATE;
    }

    _onUngrab() {
        global.display.set_cursor(Meta.Cursor.DEFAULT);
        this.emit('finished', this._result);

        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this._group.destroy();
            return GLib.SOURCE_REMOVE;
        });
    }
};
Signals.addSignalMethods(SelectArea.prototype);

var PickPixel = class {
    constructor() {
        this._result = null;

        this._group = new St.Widget({ visible: false,
                                      reactive: true });
        Main.uiGroup.add_actor(this._group);

        this._grabHelper = new GrabHelper.GrabHelper(this._group);

        this._group.connect('button-release-event',
                            this._onButtonRelease.bind(this));

        let constraint = new Clutter.BindConstraint({ source: global.stage,
                                                      coordinate: Clutter.BindCoordinate.ALL });
        this._group.add_constraint(constraint);
    }

    show() {
        if (!this._grabHelper.grab({ actor: this._group,
                                     onUngrab: this._onUngrab.bind(this) }))
            return;

        global.display.set_cursor(Meta.Cursor.CROSSHAIR);
        Main.uiGroup.set_child_above_sibling(this._group, null);
        this._group.visible = true;
    }

    _onButtonRelease(actor, event) {
        this._result = event.get_coords();
        this._grabHelper.ungrab();
        return Clutter.EVENT_PROPAGATE;
    }

    _onUngrab() {
        global.display.set_cursor(Meta.Cursor.DEFAULT);
        this.emit('finished', this._result);

        GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this._group.destroy();
            return GLib.SOURCE_REMOVE;
        });
    }
};
Signals.addSignalMethods(PickPixel.prototype);

var FLASHSPOT_ANIMATION_OUT_TIME = 0.5; // seconds

var Flashspot = class extends Lightbox.Lightbox {
    constructor(area) {
        super(Main.uiGroup, { inhibitEvents: true,
                              width: area.width,
                              height: area.height });

        this.actor.style_class = 'flashspot';
        this.actor.set_position(area.x, area.y);
    }

    fire(doneCallback) {
        this.actor.show();
        this.actor.opacity = 255;
        Tweener.addTween(this.actor,
                         { opacity: 0,
                           time: FLASHSPOT_ANIMATION_OUT_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               if (doneCallback)
                                   doneCallback();
                               this.destroy();
                           }
                         });
    }
};
(uuay)workspacesView.js�t// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const Tweener = imports.ui.tweener;
const WindowManager = imports.ui.windowManager;
const Workspace = imports.ui.workspace;

var WORKSPACE_SWITCH_TIME = 0.25;

var AnimationType = {
    ZOOM: 0,
    FADE: 1
};

const MUTTER_SCHEMA = 'org.gnome.mutter';

var WorkspacesViewBase = class {
    constructor(monitorIndex) {
        this.actor = new St.Widget({ style_class: 'workspaces-view',
                                     reactive: true });
        this.actor.connect('destroy', this._onDestroy.bind(this));
        global.focus_manager.add_group(this.actor);

        // The actor itself isn't a drop target, so we don't want to pick on its area
        this.actor.set_size(0, 0);

        this._monitorIndex = monitorIndex;

        this._fullGeometry = null;
        this._actualGeometry = null;

        this._inDrag = false;
        this._windowDragBeginId = Main.overview.connect('window-drag-begin', this._dragBegin.bind(this));
        this._windowDragEndId = Main.overview.connect('window-drag-end', this._dragEnd.bind(this));
    }

    _onDestroy() {
        this._dragEnd();

        if (this._windowDragBeginId > 0) {
            Main.overview.disconnect(this._windowDragBeginId);
            this._windowDragBeginId = 0;
        }
        if (this._windowDragEndId > 0) {
            Main.overview.disconnect(this._windowDragEndId);
            this._windowDragEndId = 0;
        }
    }

    _dragBegin(overview, window) {
        this._inDrag = true;
        this._setReservedSlot(window);
    }

    _dragEnd() {
        this._inDrag = false;
        this._setReservedSlot(null);
    }

    destroy() {
        this.actor.destroy();
    }

    setFullGeometry(geom) {
        this._fullGeometry = geom;
        this._syncFullGeometry();
    }

    setActualGeometry(geom) {
        this._actualGeometry = geom;
        this._syncActualGeometry();
    }
};

var WorkspacesView = class extends WorkspacesViewBase {
    constructor(monitorIndex) {
        let workspaceManager = global.workspace_manager;

        super(monitorIndex);

        this._animating = false; // tweening
        this._scrolling = false; // swipe-scrolling
        this._gestureActive = false; // touch(pad) gestures
        this._animatingScroll = false; // programatically updating the adjustment

        let activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
        this.scrollAdjustment = new St.Adjustment({ value: activeWorkspaceIndex,
                                                    lower: 0,
                                                    page_increment: 1,
                                                    page_size: 1,
                                                    step_increment: 0,
                                                    upper: workspaceManager.n_workspaces });
        this.scrollAdjustment.connect('notify::value',
                                      this._onScroll.bind(this));

        this._workspaces = [];
        this._updateWorkspaces();
        this._updateWorkspacesId =
            workspaceManager.connect('notify::n-workspaces',
                                     this._updateWorkspaces.bind(this));

        this._overviewShownId =
            Main.overview.connect('shown', () => {
                this.actor.set_clip(this._fullGeometry.x, this._fullGeometry.y,
                                    this._fullGeometry.width, this._fullGeometry.height);
            });

        this._switchWorkspaceNotifyId =
            global.window_manager.connect('switch-workspace',
                                          this._activeWorkspaceChanged.bind(this));
    }

    _setReservedSlot(window) {
        for (let i = 0; i < this._workspaces.length; i++)
            this._workspaces[i].setReservedSlot(window);
    }

    _syncFullGeometry() {
        for (let i = 0; i < this._workspaces.length; i++)
            this._workspaces[i].setFullGeometry(this._fullGeometry);
    }

    _syncActualGeometry() {
        for (let i = 0; i < this._workspaces.length; i++)
            this._workspaces[i].setActualGeometry(this._actualGeometry);
    }

    getActiveWorkspace() {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();
        return this._workspaces[active];
    }

    animateToOverview(animationType) {
        for (let w = 0; w < this._workspaces.length; w++) {
            if (animationType == AnimationType.ZOOM)
                this._workspaces[w].zoomToOverview();
            else
                this._workspaces[w].fadeToOverview();
        }
        this._updateWorkspaceActors(false);
    }

    animateFromOverview(animationType) {
        this.actor.remove_clip();

        for (let w = 0; w < this._workspaces.length; w++) {
            if (animationType == AnimationType.ZOOM)
                this._workspaces[w].zoomFromOverview();
            else
                this._workspaces[w].fadeFromOverview();
        }
    }

    syncStacking(stackIndices) {
        for (let i = 0; i < this._workspaces.length; i++)
            this._workspaces[i].syncStacking(stackIndices);
    }

    _scrollToActive() {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();

        this._updateWorkspaceActors(true);
        this._updateScrollAdjustment(active);
    }

    // Update workspace actors parameters
    // @showAnimation: iff %true, transition between states
    _updateWorkspaceActors(showAnimation) {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();

        this._animating = showAnimation;

        for (let w = 0; w < this._workspaces.length; w++) {
            let workspace = this._workspaces[w];

            Tweener.removeTweens(workspace.actor);

            let params = {};
            if (workspaceManager.layout_rows == -1)
                params.y = (w - active) * this._fullGeometry.height;
            else if (this.actor.text_direction == Clutter.TextDirection.RTL)
                params.x = (active - w) * this._fullGeometry.width;
            else
                params.x = (w - active) * this._fullGeometry.width;

            if (showAnimation) {
                let tweenParams = Object.assign(params, {
                    time: WORKSPACE_SWITCH_TIME,
                    transition: 'easeOutQuad'
                });
                // we have to call _updateVisibility() once before the
                // animation and once afterwards - it does not really
                // matter which tween we use, so we pick the first one ...
                if (w == 0) {
                    this._updateVisibility();
                    tweenParams.onComplete = () => {
                        this._animating = false;
                        this._updateVisibility();
                    };
                }
                Tweener.addTween(workspace.actor, tweenParams);
            } else {
                workspace.actor.set(params);
                if (w == 0)
                    this._updateVisibility();
            }
        }
    }

    _updateVisibility() {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();

        for (let w = 0; w < this._workspaces.length; w++) {
            let workspace = this._workspaces[w];
            if (this._animating || this._scrolling || this._gestureActive) {
                workspace.actor.show();
            } else {
                if (this._inDrag)
                    workspace.actor.visible = (Math.abs(w - active) <= 1);
                else
                    workspace.actor.visible = (w == active);
            }
        }
    }

    _updateScrollAdjustment(index) {
        if (this._scrolling || this._gestureActive)
            return;

        this._animatingScroll = true;

        Tweener.addTween(this.scrollAdjustment, {
            value: index,
            time: WORKSPACE_SWITCH_TIME,
            transition: 'easeOutQuad',
            onComplete: () => {
                this._animatingScroll = false;
            }
        });
    }

    _updateWorkspaces() {
        let workspaceManager = global.workspace_manager;
        let newNumWorkspaces = workspaceManager.n_workspaces;

        this.scrollAdjustment.upper = newNumWorkspaces;

        let needsUpdate = false;
        for (let j = 0; j < newNumWorkspaces; j++) {
            let metaWorkspace = workspaceManager.get_workspace_by_index(j);
            let workspace;

            if (j >= this._workspaces.length) { /* added */
                workspace = new Workspace.Workspace(metaWorkspace, this._monitorIndex);
                this.actor.add_actor(workspace.actor);
                this._workspaces[j] = workspace;
            } else  {
                workspace = this._workspaces[j];

                if (workspace.metaWorkspace != metaWorkspace) { /* removed */
                    workspace.destroy();
                    this._workspaces.splice(j, 1);
                } /* else kept */
            }
        }

        if (this._fullGeometry) {
            this._updateWorkspaceActors(false);
            this._syncFullGeometry();
        }
        if (this._actualGeometry)
            this._syncActualGeometry();
    }

    _activeWorkspaceChanged(wm, from, to, direction) {
        if (this._scrolling)
            return;

        this._scrollToActive();
    }

    _onDestroy() {
        super._onDestroy();

        this.scrollAdjustment.run_dispose();
        Main.overview.disconnect(this._overviewShownId);
        global.window_manager.disconnect(this._switchWorkspaceNotifyId);
        let workspaceManager = global.workspace_manager;
        workspaceManager.disconnect(this._updateWorkspacesId);
    }

    startSwipeScroll() {
        this._scrolling = true;
    }

    endSwipeScroll() {
        this._scrolling = false;

        // Make sure title captions etc are shown as necessary
        this._scrollToActive();
        this._updateVisibility();
    }

    startTouchGesture() {
        this._gestureActive = true;
    }

    endTouchGesture() {
        this._gestureActive = false;

        // Make sure title captions etc are shown as necessary
        this._scrollToActive();
        this._updateVisibility();
    }

    // sync the workspaces' positions to the value of the scroll adjustment
    // and change the active workspace if appropriate
    _onScroll(adj) {
        if (this._animatingScroll)
            return;

        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();
        let current = Math.round(adj.value);

        if (active != current && !this._gestureActive) {
            if (!this._workspaces[current]) {
                // The current workspace was destroyed. This could happen
                // when you are on the last empty workspace, and consolidate
                // windows using the thumbnail bar.
                // In that case, the intended behavior is to stay on the empty
                // workspace, which is the last one, so pick it.
                current = this._workspaces.length - 1;
            }

            let metaWorkspace = this._workspaces[current].metaWorkspace;
            metaWorkspace.activate(global.get_current_time());
        }

        if (adj.upper == 1)
            return;

        let last = this._workspaces.length - 1;

        if (workspaceManager.layout_rows == -1) {
            let firstWorkspaceY = this._workspaces[0].actor.y;
            let lastWorkspaceY = this._workspaces[last].actor.y;
            let workspacesHeight = lastWorkspaceY - firstWorkspaceY;

            let currentY = firstWorkspaceY;
            let newY = -adj.value / (adj.upper - 1) * workspacesHeight;

            let dy = newY - currentY;

            for (let i = 0; i < this._workspaces.length; i++) {
                this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
                this._workspaces[i].actor.y += dy;
            }
        } else {
            let firstWorkspaceX = this._workspaces[0].actor.x;
            let lastWorkspaceX = this._workspaces[last].actor.x;
            let workspacesWidth = lastWorkspaceX - firstWorkspaceX;

            let currentX = firstWorkspaceX;
            let newX = -adj.value / (adj.upper - 1) * workspacesWidth;

            let dx = newX - currentX;

            for (let i = 0; i < this._workspaces.length; i++) {
                this._workspaces[i].actor.visible = Math.abs(i - adj.value) <= 1;
                this._workspaces[i].actor.x += dx;
            }
        }
    }
};
Signals.addSignalMethods(WorkspacesView.prototype);

var ExtraWorkspaceView = class extends WorkspacesViewBase {
    constructor(monitorIndex) {
        super(monitorIndex);
        this._workspace = new Workspace.Workspace(null, monitorIndex);
        this.actor.add_actor(this._workspace.actor);
    }

    _setReservedSlot(window) {
        this._workspace.setReservedSlot(window);
    }

    _syncFullGeometry() {
        this._workspace.setFullGeometry(this._fullGeometry);
    }

    _syncActualGeometry() {
        this._workspace.setActualGeometry(this._actualGeometry);
    }

    getActiveWorkspace() {
        return this._workspace;
    }

    animateToOverview(animationType) {
        if (animationType == AnimationType.ZOOM)
            this._workspace.zoomToOverview();
        else
            this._workspace.fadeToOverview();
    }

    animateFromOverview(animationType) {
        if (animationType == AnimationType.ZOOM)
            this._workspace.zoomFromOverview();
        else
            this._workspace.fadeFromOverview();
    }

    syncStacking(stackIndices) {
        this._workspace.syncStacking(stackIndices);
    }

    startSwipeScroll() {
    }

    endSwipeScroll() {
    }

    startTouchGesture() {
    }

    endTouchGesture() {
    }
};

var DelegateFocusNavigator = GObject.registerClass(
class DelegateFocusNavigator extends St.Widget {
    vfunc_navigate_focus(from, direction) {
        return this._delegate.navigateFocus(from, direction);
    }
});

var WorkspacesDisplay = class {
    constructor() {
        this.actor = new DelegateFocusNavigator({ clip_to_allocation: true });
        this.actor._delegate = this;
        this.actor.connect('notify::allocation', this._updateWorkspacesActualGeometry.bind(this));
        this.actor.connect('parent-set', this._parentSet.bind(this));
        this.actor.connect('destroy', () => {
            if (this._laterId)
                Meta.later_remove(this._laterId);
            this._laterId = 0;
        });

        let clickAction = new Clutter.ClickAction();
        clickAction.connect('clicked', action => {
            // Only switch to the workspace when there's no application
            // windows open. The problem is that it's too easy to miss
            // an app window and get the wrong one focused.
            let event = Clutter.get_current_event();
            let index = this._getMonitorIndexForEvent(event);
            if ((action.get_button() == 1 || action.get_button() == 0) &&
                this._workspacesViews[index].getActiveWorkspace().isEmpty())
                Main.overview.hide();
        });
        Main.overview.addAction(clickAction);
        this.actor.bind_property('mapped', clickAction, 'enabled', GObject.BindingFlags.SYNC_CREATE);

        let panAction = new Clutter.PanAction({ threshold_trigger_edge: Clutter.GestureTriggerEdge.AFTER });
        panAction.connect('pan', this._onPan.bind(this));
        panAction.connect('gesture-begin', () => {
            if (this._workspacesOnlyOnPrimary) {
                let event = Clutter.get_current_event();
                if (this._getMonitorIndexForEvent(event) != this._primaryIndex)
                    return false;
            }

            this._startSwipeScroll();
            return true;
        });
        panAction.connect('gesture-cancel', () => {
            clickAction.release();
            this._endSwipeScroll();
        });
        panAction.connect('gesture-end', () => {
            clickAction.release();
            this._endSwipeScroll();
        });
        Main.overview.addAction(panAction);
        this.actor.bind_property('mapped', panAction, 'enabled', GObject.BindingFlags.SYNC_CREATE);

        let allowedModes = Shell.ActionMode.OVERVIEW;
        let switchGesture = new WindowManager.WorkspaceSwitchAction(allowedModes);
        switchGesture.connect('motion', this._onSwitchWorkspaceMotion.bind(this));
        switchGesture.connect('activated', this._onSwitchWorkspaceActivated.bind(this));
        switchGesture.connect('cancel', this._endTouchGesture.bind(this));
        Main.overview.addAction(switchGesture);
        this.actor.bind_property('mapped', switchGesture, 'enabled', GObject.BindingFlags.SYNC_CREATE);

        switchGesture = new WindowManager.TouchpadWorkspaceSwitchAction(global.stage, allowedModes);
        switchGesture.connect('motion', this._onSwitchWorkspaceMotion.bind(this));
        switchGesture.connect('activated', this._onSwitchWorkspaceActivated.bind(this));
        switchGesture.connect('cancel', this._endTouchGesture.bind(this));
        this.actor.connect('notify::mapped', () => {
            switchGesture.enabled = this.actor.mapped;
        });

        this._primaryIndex = Main.layoutManager.primaryIndex;

        this._workspacesViews = [];
        this._primaryScrollAdjustment = null;
        switchGesture.enabled = this.actor.mapped;

        this._settings = new Gio.Settings({ schema_id: MUTTER_SCHEMA });
        this._settings.connect('changed::workspaces-only-on-primary',
                               this._workspacesOnlyOnPrimaryChanged.bind(this));
        this._workspacesOnlyOnPrimaryChanged();

        this._switchWorkspaceNotifyId = 0;

        this._notifyOpacityId = 0;
        this._restackedNotifyId = 0;
        this._scrollEventId = 0;
        this._keyPressEventId = 0;

        this._actualGeometry = null;
        this._fullGeometry = null;
    }

    _onPan(action) {
        let [dist, dx, dy] = action.get_motion_delta(0);
        let adjustment = this._scrollAdjustment;
        if (global.workspace_manager.layout_rows == -1)
            adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
        else if (this.actor.text_direction == Clutter.TextDirection.RTL)
            adjustment.value += (dx / this.actor.width) * adjustment.page_size;
        else
            adjustment.value -= (dx / this.actor.width) * adjustment.page_size;
        return false;
    }

    _startSwipeScroll() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].startSwipeScroll();
    }

    _endSwipeScroll() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].endSwipeScroll();
    }

    _startTouchGesture() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].startTouchGesture();
    }

    _endTouchGesture() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].endTouchGesture();
    }

    _onSwitchWorkspaceMotion(action, xRel, yRel) {
        // We don't have a way to hook into start of touchpad actions,
        // luckily this is safe to call repeatedly.
        this._startTouchGesture();

        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();
        let adjustment = this._scrollAdjustment;
        if (workspaceManager.layout_rows == -1)
            adjustment.value = (active - yRel / this.actor.height) * adjustment.page_size;
        else if (this.actor.text_direction == Clutter.TextDirection.RTL)
            adjustment.value = (active + xRel / this.actor.width) * adjustment.page_size;
        else
            adjustment.value = (active - xRel / this.actor.width) * adjustment.page_size;
    }

    _onSwitchWorkspaceActivated(action, direction) {
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        let newWs = activeWorkspace.get_neighbor(direction);
        if (newWs != activeWorkspace)
            newWs.activate(global.get_current_time());

        this._endTouchGesture();
    }

    navigateFocus(from, direction) {
        return this._getPrimaryView().actor.navigate_focus(from, direction, false);
    }

    show(fadeOnPrimary) {
        this._updateWorkspacesViews();

        if (this._actualGeometry && this._fullGeometry) {
            for (let i = 0; i < this._workspacesViews.length; i++) {
                let animationType;
                if (fadeOnPrimary && i == this._primaryIndex)
                    animationType = AnimationType.FADE;
                else
                    animationType = AnimationType.ZOOM;
                this._workspacesViews[i].animateToOverview(animationType);
            }
        }

        this._restackedNotifyId =
            Main.overview.connect('windows-restacked',
                                  this._onRestacked.bind(this));
        if (this._scrollEventId == 0)
            this._scrollEventId = Main.overview.connect('scroll-event', this._onScrollEvent.bind(this));

        if (this._keyPressEventId == 0)
            this._keyPressEventId = global.stage.connect('key-press-event', this._onKeyPressEvent.bind(this));
    }

    animateFromOverview(fadeOnPrimary) {
        for (let i = 0; i < this._workspacesViews.length; i++) {
            let animationType;
            if (fadeOnPrimary && i == this._primaryIndex)
                animationType = AnimationType.FADE;
            else
                animationType = AnimationType.ZOOM;
            this._workspacesViews[i].animateFromOverview(animationType);
        }
    }

    hide() {
        if (this._restackedNotifyId > 0){
            Main.overview.disconnect(this._restackedNotifyId);
            this._restackedNotifyId = 0;
        }
        if (this._scrollEventId > 0) {
            Main.overview.disconnect(this._scrollEventId);
            this._scrollEventId = 0;
        }
        if (this._keyPressEventId > 0) {
            global.stage.disconnect(this._keyPressEventId);
            this._keyPressEventId = 0;
        }
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].destroy();
        this._workspacesViews = [];
    }

    _workspacesOnlyOnPrimaryChanged() {
        this._workspacesOnlyOnPrimary = this._settings.get_boolean('workspaces-only-on-primary');

        if (!Main.overview.visible)
            return;

        this._updateWorkspacesViews();
    }

    _updateWorkspacesViews() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].destroy();

        this._primaryIndex = Main.layoutManager.primaryIndex;
        this._workspacesViews = [];
        let monitors = Main.layoutManager.monitors;
        for (let i = 0; i < monitors.length; i++) {
            let view;
            if (this._workspacesOnlyOnPrimary && i != this._primaryIndex)
                view = new ExtraWorkspaceView(i);
            else
                view = new WorkspacesView(i);

            view.actor.connect('scroll-event', this._onScrollEvent.bind(this));
            if (i == this._primaryIndex) {
                this._scrollAdjustment = view.scrollAdjustment;
                this._scrollAdjustment.connect('notify::value',
                                               this._scrollValueChanged.bind(this));
            }

            // HACK: Avoid spurious allocation changes while updating views
            view.actor.hide();

            this._workspacesViews.push(view);
            Main.layoutManager.overviewGroup.add_actor(view.actor);
        }

        this._workspacesViews.forEach(v => v.actor.show());

        if (this._fullGeometry)
            this._syncWorkspacesFullGeometry();
        if (this._actualGeometry)
            this._syncWorkspacesActualGeometry();
    }

    _scrollValueChanged() {
        for (let i = 0; i < this._workspacesViews.length; i++) {
            if (i == this._primaryIndex)
                continue;

            let adjustment = this._workspacesViews[i].scrollAdjustment;
            if (!adjustment)
                continue;

            // the adjustments work in terms of workspaces, so the
            // values map directly
            adjustment.value = this._scrollAdjustment.value;
        }
    }

    _getMonitorIndexForEvent(event) {
        let [x, y] = event.get_coords();
        let rect = new Meta.Rectangle({ x: x, y: y, width: 1, height: 1 });
        return global.display.get_monitor_index_for_rect(rect);
    }

    _getPrimaryView() {
        if (!this._workspacesViews.length)
            return null;
        return this._workspacesViews[this._primaryIndex];
    }

    activeWorkspaceHasMaximizedWindows() {
        return this._getPrimaryView().getActiveWorkspace().hasMaximizedWindows();
    }

    _parentSet(actor, oldParent) {
        if (oldParent && this._notifyOpacityId)
            oldParent.disconnect(this._notifyOpacityId);
        this._notifyOpacityId = 0;

        if (!this.actor.get_parent())
            return;

        this._laterId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            let newParent = this.actor.get_parent();
            if (!newParent)
                return;

            // This is kinda hackish - we want the primary view to
            // appear as parent of this.actor, though in reality it
            // is added directly to Main.layoutManager.overviewGroup
            this._notifyOpacityId = newParent.connect('notify::opacity', () => {
                let opacity = this.actor.get_parent().opacity;
                let primaryView = this._getPrimaryView();
                if (!primaryView)
                    return;
                primaryView.actor.opacity = opacity;
                primaryView.actor.visible = opacity != 0;
            });
        });
    }

    // This geometry should always be the fullest geometry
    // the workspaces switcher can ever be allocated, as if
    // the sliding controls were never slid in at all.
    setWorkspacesFullGeometry(geom) {
        this._fullGeometry = geom;
        this._syncWorkspacesFullGeometry();
    }

    _syncWorkspacesFullGeometry() {
        if (!this._workspacesViews.length)
            return;

        let monitors = Main.layoutManager.monitors;
        for (let i = 0; i < monitors.length; i++) {
            let geometry = (i == this._primaryIndex) ? this._fullGeometry : monitors[i];
            this._workspacesViews[i].setFullGeometry(geometry);
        }
    }

    _updateWorkspacesActualGeometry() {
        const [x, y] = this.actor.get_transformed_position();
        const width = this.actor.allocation.get_width();
        const height = this.actor.allocation.get_height();

        this._actualGeometry = { x, y, width, height };
        this._syncWorkspacesActualGeometry();
    }

    _syncWorkspacesActualGeometry() {
        if (!this._workspacesViews.length)
            return;

        let monitors = Main.layoutManager.monitors;
        for (let i = 0; i < monitors.length; i++) {
            let geometry = i === this._primaryIndex ? this._actualGeometry : monitors[i];
            this._workspacesViews[i].setActualGeometry(geometry);
        }
    }

    _onRestacked(overview, stackIndices) {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].syncStacking(stackIndices);
    }

    _onScrollEvent(actor, event) {
        if (!this.actor.mapped)
            return Clutter.EVENT_PROPAGATE;

        if (this._workspacesOnlyOnPrimary &&
            this._getMonitorIndexForEvent(event) != this._primaryIndex)
            return Clutter.EVENT_PROPAGATE;

        let workspaceManager = global.workspace_manager;
        let activeWs = workspaceManager.get_active_workspace();
        let ws;
        switch (event.get_scroll_direction()) {
        case Clutter.ScrollDirection.UP:
            ws = activeWs.get_neighbor(Meta.MotionDirection.UP);
            break;
        case Clutter.ScrollDirection.DOWN:
            ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
            break;
        case Clutter.ScrollDirection.LEFT:
            ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
            break;
        case Clutter.ScrollDirection.RIGHT:
            ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
            break;
        default:
            return Clutter.EVENT_PROPAGATE;
        }
        Main.wm.actionMoveWorkspace(ws);
        return Clutter.EVENT_STOP;
    }

    _onKeyPressEvent(actor, event) {
        if (!this.actor.mapped)
            return Clutter.EVENT_PROPAGATE;
        let workspaceManager = global.workspace_manager;
        let activeWs = workspaceManager.get_active_workspace();
        let ws;
        switch (event.get_key_symbol()) {
        case Clutter.KEY_Page_Up:
            ws = activeWs.get_neighbor(Meta.MotionDirection.UP);
            break;
        case Clutter.KEY_Page_Down:
            ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
            break;
        default:
            return Clutter.EVENT_PROPAGATE;
        }
        Main.wm.actionMoveWorkspace(ws);
        return Clutter.EVENT_STOP;
    }
};
Signals.addSignalMethods(WorkspacesDisplay.prototype);
(uuay)authList.js+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
 * Copyright 2017 Red Hat, Inc
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

const Clutter = imports.gi.Clutter;
const GObject = imports.gi.GObject;
const Gtk = imports.gi.Gtk;
const Lang = imports.lang;
const Meta = imports.gi.Meta;
const Signals = imports.signals;
const St = imports.gi.St;

const Tweener = imports.ui.tweener;

const _SCROLL_ANIMATION_TIME = 0.5;

const AuthListItem = new Lang.Class({
    Name: 'AuthListItem',

    _init(key, text) {
        this.key = key;
        let label = new St.Label({ style_class: 'auth-list-item-label',
                                   y_align: Clutter.ActorAlign.CENTER });
        label.text = text;

        this.actor = new St.Button({ style_class: 'login-dialog-user-list-item',
                                     button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                     can_focus: true,
                                     child: label,
                                     reactive: true,
                                     x_align: St.Align.START,
                                     x_fill: true });

        this.actor.connect('key-focus-in', () => {
            this._setSelected(true);
        });
        this.actor.connect('key-focus-out', () => {
            this._setSelected(false);
        });
        this.actor.connect('notify::hover', () => {
            this._setSelected(this.actor.hover);
        });

        this.actor.connect('clicked', this._onClicked.bind(this));
    },

    _onClicked() {
        this.emit('activate');
    },

    _setSelected(selected) {
        if (selected) {
            this.actor.add_style_pseudo_class('selected');
            this.actor.grab_key_focus();
        } else {
            this.actor.remove_style_pseudo_class('selected');
        }
    }
});
Signals.addSignalMethods(AuthListItem.prototype);

var AuthList = new Lang.Class({
    Name: 'AuthList',

    _init() {
        this.actor = new St.BoxLayout({ vertical: true,
                                        style_class: 'login-dialog-auth-list-layout' });

        this.label = new St.Label({ style_class: 'prompt-dialog-headline' });
        this.actor.add_actor(this.label);

        this._scrollView = new St.ScrollView({ style_class: 'login-dialog-user-list-view'});
        this._scrollView.set_policy(Gtk.PolicyType.NEVER,
                                    Gtk.PolicyType.AUTOMATIC);
        this.actor.add_actor(this._scrollView);

        this._box = new St.BoxLayout({ vertical: true,
                                       style_class: 'login-dialog-user-list',
                                       pseudo_class: 'expanded' });

        this._scrollView.add_actor(this._box);
        this._items = {};

        this.actor.connect('key-focus-in', this._moveFocusToItems.bind(this));
    },

    _moveFocusToItems() {
        let hasItems = Object.keys(this._items).length > 0;

        if (!hasItems)
            return;

        if (global.stage.get_key_focus() != this.actor)
            return;

        let focusSet = this.actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
        if (!focusSet) {
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this._moveFocusToItems();
                return false;
            });
        }
    },

    _onItemActivated(activatedItem) {
        this.emit('activate', activatedItem.key);
    },

    scrollToItem(item) {
        let box = item.actor.get_allocation_box();

        let adjustment = this._scrollView.get_vscroll_bar().get_adjustment();

        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
        Tweener.removeTweens(adjustment);
        Tweener.addTween (adjustment,
                          { value: value,
                            time: _SCROLL_ANIMATION_TIME,
                            transition: 'easeOutQuad' });
    },

    jumpToItem(item) {
        let box = item.actor.get_allocation_box();

        let adjustment = this._scrollView.get_vscroll_bar().get_adjustment();

        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);

        adjustment.set_value(value);
    },

    getItem(key) {
        let item = this._items[key];

        if (!item)
            return null;

        return item;
    },

    addItem(key, text) {
        this.removeItem(key);

        let item = new AuthListItem(key, text);
        this._box.add(item.actor, { x_fill: true });

        this._items[key] = item;

        item.connect('activate',
                     this._onItemActivated.bind(this));

        // Try to keep the focused item front-and-center
        item.actor.connect('key-focus-in',
                           () => { this.scrollToItem(item); });

        this._moveFocusToItems();

        this.emit('item-added', item);
    },

    removeItem(key) {
        let item = this._items[key];

        if (!item)
            return;

        item.actor.destroy();
        delete this._items[key];
    },

    numItems() {
        return Object.keys(this._items).length;
    },

    clear() {
        this.label.text = "";
        this._box.destroy_all_children();
        this._items = {};
    }
});
Signals.addSignalMethods(AuthList.prototype);
(uuay)slider.jsd/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const { Atk, Clutter } = imports.gi;
const Signals = imports.signals;

const BarLevel = imports.ui.barLevel;

var SLIDER_SCROLL_STEP = 0.02; /* Slider scrolling step in % */

var Slider = class extends BarLevel.BarLevel {
    constructor(value) {
        let params = {
            styleClass: 'slider',
            canFocus: true,
            reactive: true,
            accessibleRole: Atk.Role.SLIDER,
        }
        super(value, params)

        this.actor.connect('button-press-event', this._startDragging.bind(this));
        this.actor.connect('touch-event', this._touchDragging.bind(this));
        this.actor.connect('scroll-event', this._onScrollEvent.bind(this));
        this.actor.connect('key-press-event', this.onKeyPressEvent.bind(this));

        this._releaseId = this._motionId = 0;
        this._dragging = false;

        this._customAccessible.connect('get-minimum-increment', this._getMinimumIncrement.bind(this));
    }

    _barLevelRepaint(area) {
        super._barLevelRepaint(area);

        // Add handle
        let cr = area.get_context();
        let themeNode = area.get_theme_node();
        let [width, height] = area.get_surface_size();

        let handleRadius = themeNode.get_length('-slider-handle-radius');

        let handleBorderWidth = themeNode.get_length('-slider-handle-border-width');
        let [hasHandleColor, handleBorderColor] =
            themeNode.lookup_color('-slider-handle-border-color', false);

        const TAU = Math.PI * 2;

        let handleX = handleRadius + (width - 2 * handleRadius) * this._value / this._maxValue;
        let handleY = height / 2;

        let color = themeNode.get_foreground_color();
        Clutter.cairo_set_source_color(cr, color);
        cr.arc(handleX, handleY, handleRadius, 0, 2 * Math.PI);
        cr.fillPreserve();
        if (hasHandleColor && handleBorderWidth) {
            Clutter.cairo_set_source_color(cr, handleBorderColor);
            cr.setLineWidth(handleBorderWidth);
            cr.stroke();
        }
        cr.$dispose();
    }

    _startDragging(actor, event) {
        return this.startDragging(event);
    }

    startDragging(event) {
        if (this._dragging)
            return Clutter.EVENT_PROPAGATE;

        this._dragging = true;

        let device = event.get_device();
        let sequence = event.get_event_sequence();

        if (sequence != null)
            device.sequence_grab(sequence, this.actor);
        else
            device.grab(this.actor);

        this._grabbedDevice = device;
        this._grabbedSequence = sequence;

        if (sequence == null) {
            this._releaseId = this.actor.connect('button-release-event', this._endDragging.bind(this));
            this._motionId = this.actor.connect('motion-event', this._motionEvent.bind(this));
        }

        // We need to emit 'drag-begin' before moving the handle to make
        // sure that no 'value-changed' signal is emitted before this one.
        this.emit('drag-begin');

        let absX, absY;
        [absX, absY] = event.get_coords();
        this._moveHandle(absX, absY);
        return Clutter.EVENT_STOP;
    }

    _endDragging() {
        if (this._dragging) {
            if (this._releaseId)
                this.actor.disconnect(this._releaseId);
            if (this._motionId)
                this.actor.disconnect(this._motionId);

            if (this._grabbedSequence != null)
                this._grabbedDevice.sequence_ungrab(this._grabbedSequence);
            else
                this._grabbedDevice.ungrab();

            this._grabbedSequence = null;
            this._grabbedDevice = null;
            this._dragging = false;

            this.emit('drag-end');
        }
        return Clutter.EVENT_STOP;
    }

    _touchDragging(actor, event) {
        let device = event.get_device();
        let sequence = event.get_event_sequence();

        if (!this._dragging &&
            event.type() == Clutter.EventType.TOUCH_BEGIN) {
            this.startDragging(event);
            return Clutter.EVENT_STOP;
        } else if (device.sequence_get_grabbed_actor(sequence) == actor) {
            if (event.type() == Clutter.EventType.TOUCH_UPDATE)
                return this._motionEvent(actor, event);
            else if (event.type() == Clutter.EventType.TOUCH_END)
                return this._endDragging();
        }

        return Clutter.EVENT_PROPAGATE;
    }

    scroll(event) {
        let direction = event.get_scroll_direction();
        let delta;

        if (event.is_pointer_emulated())
            return Clutter.EVENT_PROPAGATE;

        if (direction == Clutter.ScrollDirection.DOWN) {
            delta = -SLIDER_SCROLL_STEP;
        } else if (direction == Clutter.ScrollDirection.UP) {
            delta = +SLIDER_SCROLL_STEP;
        } else if (direction == Clutter.ScrollDirection.SMOOTH) {
            let [dx, dy] = event.get_scroll_delta();
            // Even though the slider is horizontal, use dy to match
            // the UP/DOWN above.
            delta = -dy * SLIDER_SCROLL_STEP;
        }

        this._value = Math.min(Math.max(0, this._value + delta), this._maxValue);

        this.actor.queue_repaint();
        this.emit('value-changed', this._value);
        return Clutter.EVENT_STOP;
    }

    _onScrollEvent(actor, event) {
        return this.scroll(event);
    }

    _motionEvent(actor, event) {
        let absX, absY;
        [absX, absY] = event.get_coords();
        this._moveHandle(absX, absY);
        return Clutter.EVENT_STOP;
    }

    onKeyPressEvent(actor, event) {
        let key = event.get_key_symbol();
        if (key == Clutter.KEY_Right || key == Clutter.KEY_Left) {
            let delta = key == Clutter.KEY_Right ? 0.1 : -0.1;
            this._value = Math.max(0, Math.min(this._value + delta, this._maxValue));
            this.actor.queue_repaint();
            this.emit('drag-begin');
            this.emit('value-changed', this._value);
            this.emit('drag-end');
            return Clutter.EVENT_STOP;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _moveHandle(absX, absY) {
        let relX, relY, sliderX, sliderY;
        [sliderX, sliderY] = this.actor.get_transformed_position();
        relX = absX - sliderX;
        relY = absY - sliderY;

        let width = this._barLevelWidth;
        let handleRadius = this.actor.get_theme_node().get_length('-slider-handle-radius');

        let newvalue;
        if (relX < handleRadius)
            newvalue = 0;
        else if (relX > width - handleRadius)
            newvalue = 1;
        else
            newvalue = (relX - handleRadius) / (width - 2 * handleRadius);
        this._value = newvalue * this._maxValue;
        this.actor.queue_repaint();
        this.emit('value-changed', this._value);
    }

    _getMinimumIncrement(actor) {
        return 0.1;
    }
};
Signals.addSignalMethods(Slider.prototype);
(uuay)screenShield.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { AccountsService, Clutter, Cogl, Gio, GLib,
        GnomeDesktop, GObject, Meta, Shell, St } = imports.gi;
const Cairo = imports.cairo;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const TweenerEquations = imports.tweener.equations;

const Background = imports.ui.background;
const GnomeSession = imports.misc.gnomeSession;
const Layout = imports.ui.layout;
const OVirt = imports.gdm.oVirt;
const LoginManager = imports.misc.loginManager;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const MessageTray = imports.ui.messageTray;
const ShellDBus = imports.ui.shellDBus;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener;

const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled';
const LOCK_DELAY_KEY = 'lock-delay';

const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const DISABLE_LOCK_KEY = 'disable-lock-screen';

const LOCKED_STATE_STR = 'screenShield.locked';
// fraction of screen height the arrow must reach before completing
// the slide up automatically
var ARROW_DRAG_THRESHOLD = 0.1;

// Parameters for the arrow animation
var N_ARROWS = 3;
var ARROW_ANIMATION_TIME = 0.6;
var ARROW_ANIMATION_PEAK_OPACITY = 0.4;
var ARROW_IDLE_TIME = 30000; // ms

var SUMMARY_ICON_SIZE = 48;

// ScreenShield animation time
// - STANDARD_FADE_TIME is used when the session goes idle
// - MANUAL_FADE_TIME is used for lowering the shield when asked by the user,
//   or when cancelling the dialog
// - BACKGROUND_FADE_TIME is used when the background changes to crossfade to new background
// - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking
var STANDARD_FADE_TIME = 10;
var MANUAL_FADE_TIME = 0.3;
var BACKGROUND_FADE_TIME = 1.0;
var CURTAIN_SLIDE_TIME = 0.3;

var Clock = class {
    constructor() {
        this.actor = new St.BoxLayout({ style_class: 'screen-shield-clock',
                                        vertical: true });

        this._time = new St.Label({ style_class: 'screen-shield-clock-time' });
        this._date = new St.Label({ style_class: 'screen-shield-clock-date' });

        this.actor.add(this._time, { x_align: St.Align.MIDDLE });
        this.actor.add(this._date, { x_align: St.Align.MIDDLE });

        this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
        this._wallClock.connect('notify::clock', this._updateClock.bind(this));

        this._updateClock();
    }

    _updateClock() {
        this._time.text = this._wallClock.clock;

        let date = new Date();
        /* Translators: This is a time format for a date in
           long format */
        let dateFormat = Shell.util_translate_time_string(N_("%A, %B %d"));
        this._date.text = date.toLocaleFormat(dateFormat);
    }

    destroy() {
        this.actor.destroy();
        this._wallClock.run_dispose();
    }
};

var NotificationsBox = class {
    constructor() {
        this.actor = new St.BoxLayout({ vertical: true,
                                        name: 'screenShieldNotifications',
                                        style_class: 'screen-shield-notifications-container' });

        this._scrollView = new St.ScrollView({ x_fill: false, x_align: St.Align.START,
                                               hscrollbar_policy: St.PolicyType.NEVER });
        this._notificationBox = new St.BoxLayout({ vertical: true,
                                                   style_class: 'screen-shield-notifications-container' });
        this._scrollView.add_actor(this._notificationBox);

        this.actor.add(this._scrollView, { x_fill: true, x_align: St.Align.START });

        this._sources = new Map();
        Main.messageTray.getSources().forEach(source => {
            this._sourceAdded(Main.messageTray, source, true);
        });
        this._updateVisibility();

        this._sourceAddedId = Main.messageTray.connect('source-added', this._sourceAdded.bind(this));
    }

    destroy() {
        if (this._sourceAddedId) {
            Main.messageTray.disconnect(this._sourceAddedId);
            this._sourceAddedId = 0;
        }

        let items = this._sources.entries();
        for (let [source, obj] of items) {
            this._removeSource(source, obj);
        }

        this.actor.destroy();
    }

    _updateVisibility() {
        this._notificationBox.visible =
            this._notificationBox.get_children().some(a => a.visible);

        this.actor.visible = this._notificationBox.visible;
    }

    _makeNotificationCountText(count, isChat) {
        if (isChat)
            return ngettext("%d new message", "%d new messages", count).format(count);
        else
            return ngettext("%d new notification", "%d new notifications", count).format(count);
    }

    _makeNotificationSource(source, box) {
        let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
        box.add(sourceActor, { y_fill: true });

        let textBox = new St.BoxLayout({ vertical: true });
        box.add(textBox, { y_fill: false, y_align: St.Align.START });

        let title = new St.Label({ text: source.title,
                                   style_class: 'screen-shield-notification-label' });
        textBox.add(title);

        let count = source.unseenCount;
        let countLabel = new St.Label({ text: this._makeNotificationCountText(count, source.isChat),
                                        style_class: 'screen-shield-notification-count-text' });
        textBox.add(countLabel);

        box.visible = count != 0;
        return [title, countLabel];
    }

    _makeNotificationDetailedSource(source, box) {
        let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
        let sourceBin = new St.Bin({ y_align: St.Align.START,
                                     x_align: St.Align.START,
                                     child: sourceActor });
        box.add(sourceBin);

        let textBox = new St.BoxLayout({ vertical: true });
        box.add(textBox, { y_fill: false, y_align: St.Align.START });

        let title = new St.Label({ text: source.title,
                                   style_class: 'screen-shield-notification-label' });
        textBox.add(title);

        let visible = false;
        for (let i = 0; i < source.notifications.length; i++) {
            let n = source.notifications[i];

            if (n.acknowledged)
                continue;

            let body = '';
            if (n.bannerBodyText) {
                body = n.bannerBodyMarkup ? n.bannerBodyText
                                          : GLib.markup_escape_text(n.bannerBodyText, -1);
            }

            let label = new St.Label({ style_class: 'screen-shield-notification-count-text' });
            label.clutter_text.set_markup('<b>' + n.title + '</b> ' + body);
            textBox.add(label);

            visible = true;
        }

        box.visible = visible;
        return [title, null];
    }

    _shouldShowDetails(source) {
        return source.policy.detailsInLockScreen ||
               source.narrowestPrivacyScope == MessageTray.PrivacyScope.SYSTEM;
    }

    _showSource(source, obj, box) {
        if (obj.detailed) {
            [obj.titleLabel, obj.countLabel] = this._makeNotificationDetailedSource(source, box);
        } else {
            [obj.titleLabel, obj.countLabel] = this._makeNotificationSource(source, box);
        }

        box.visible = obj.visible && (source.unseenCount > 0);
    }

    _sourceAdded(tray, source, initial) {
        let obj = {
            visible: source.policy.showInLockScreen,
            detailed: this._shouldShowDetails(source),
            sourceDestroyId: 0,
            sourceCountChangedId: 0,
            sourceTitleChangedId: 0,
            sourceUpdatedId: 0,
            sourceBox: null,
            titleLabel: null,
            countLabel: null,
        };

        obj.sourceBox = new St.BoxLayout({ style_class: 'screen-shield-notification-source',
                                           x_expand: true });
        this._showSource(source, obj, obj.sourceBox);
        this._notificationBox.add(obj.sourceBox, { x_fill: false, x_align: St.Align.START });

        obj.sourceCountChangedId = source.connect('count-updated', source => {
            this._countChanged(source, obj);
        });
        obj.sourceTitleChangedId = source.connect('title-changed', source => {
            this._titleChanged(source, obj);
        });
        obj.policyChangedId = source.policy.connect('policy-changed', (policy, key) => {
            if (key == 'show-in-lock-screen')
                this._visibleChanged(source, obj);
            else
                this._detailedChanged(source, obj);
        });
        obj.sourceDestroyId = source.connect('destroy', source => {
            this._onSourceDestroy(source, obj);
        });

        this._sources.set(source, obj);

        if (!initial) {
            // block scrollbars while animating, if they're not needed now
            let boxHeight = this._notificationBox.height;
            if (this._scrollView.height >= boxHeight)
                this._scrollView.vscrollbar_policy = St.PolicyType.NEVER;

            let widget = obj.sourceBox;
            let [, natHeight] = widget.get_preferred_height(-1);
            widget.height = 0;
            Tweener.addTween(widget,
                             { height: natHeight,
                               transition: 'easeOutQuad',
                               time: 0.25,
                               onComplete() {
                                   this._scrollView.vscrollbar_policy = St.PolicyType.AUTOMATIC;
                                   widget.set_height(-1);
                               },
                               onCompleteScope: this
                             });

            this._updateVisibility();
            if (obj.sourceBox.visible)
                this.emit('wake-up-screen');
        }
    }

    _titleChanged(source, obj) {
        obj.titleLabel.text = source.title;
    }

    _countChanged(source, obj) {
        // A change in the number of notifications may change whether we show
        // details.
        let newDetailed = this._shouldShowDetails(source);
        let oldDetailed = obj.detailed;

        obj.detailed = newDetailed;

        if (obj.detailed || oldDetailed != newDetailed) {
            // A new notification was pushed, or a previous notification was destroyed.
            // Give up, and build the list again.

            obj.sourceBox.destroy_all_children();
            obj.titleLabel = obj.countLabel = null;
            this._showSource(source, obj, obj.sourceBox);
        } else {
            let count = source.unseenCount;
            obj.countLabel.text = this._makeNotificationCountText(count, source.isChat);
        }

        obj.sourceBox.visible = obj.visible && (source.unseenCount > 0);

        this._updateVisibility();
        if (obj.sourceBox.visible)
            this.emit('wake-up-screen');
    }

    _visibleChanged(source, obj) {
        if (obj.visible == source.policy.showInLockScreen)
            return;

        obj.visible = source.policy.showInLockScreen;
        obj.sourceBox.visible = obj.visible && source.unseenCount > 0;

        this._updateVisibility();
        if (obj.sourceBox.visible)
            this.emit('wake-up-screen');
    }

    _detailedChanged(source, obj) {
        let newDetailed = this._shouldShowDetails(source);
        if (obj.detailed == newDetailed)
            return;

        obj.detailed = newDetailed;

        obj.sourceBox.destroy_all_children();
        obj.titleLabel = obj.countLabel = null;
        this._showSource(source, obj, obj.sourceBox);
    }

    _onSourceDestroy(source, obj) {
        this._removeSource(source, obj);
        this._updateVisibility();
    }

    _removeSource(source, obj) {
        obj.sourceBox.destroy();
        obj.sourceBox = obj.titleLabel = obj.countLabel = null;

        source.disconnect(obj.sourceDestroyId);
        source.disconnect(obj.sourceCountChangedId);
        source.disconnect(obj.sourceTitleChangedId);
        source.policy.disconnect(obj.policyChangedId);

        this._sources.delete(source);
    }
};
Signals.addSignalMethods(NotificationsBox.prototype);

var Arrow = GObject.registerClass(
class ScreenShieldArrow extends St.Bin {
    _init(params) {
        super._init(params);
        this.x_fill = this.y_fill = true;

        this._drawingArea = new St.DrawingArea();
        this._drawingArea.connect('repaint', this._drawArrow.bind(this));
        this.child = this._drawingArea;

        this._shadowHelper = null;
        this._shadowWidth = this._shadowHeight = 0;
    }

    _drawArrow(arrow) {
        let cr = arrow.get_context();
        let [w, h] = arrow.get_surface_size();
        let node = this.get_theme_node();
        let thickness = node.get_length('-arrow-thickness');

        Clutter.cairo_set_source_color(cr, node.get_foreground_color());

        cr.setLineCap(Cairo.LineCap.ROUND);
        cr.setLineWidth(thickness);

        cr.moveTo(thickness / 2, h - thickness / 2);
        cr.lineTo(w/2, thickness);
        cr.lineTo(w - thickness / 2, h - thickness / 2);
        cr.stroke();
        cr.$dispose();
    }

    vfunc_get_paint_volume(volume) {
        if (!super.vfunc_get_paint_volume(volume))
            return false;

        if (!this._shadow)
            return true;

        let shadow_box = new Clutter.ActorBox();
        this._shadow.get_box(this._drawingArea.get_allocation_box(), shadow_box);

        volume.set_width(Math.max(shadow_box.x2 - shadow_box.x1, volume.get_width()));
        volume.set_height(Math.max(shadow_box.y2 - shadow_box.y1, volume.get_height()));

        return true;
    }

    vfunc_style_changed() {
        let node = this.get_theme_node();
        this._shadow = node.get_shadow('-arrow-shadow');
        if (this._shadow)
            this._shadowHelper = St.ShadowHelper.new(this._shadow);
        else
            this._shadowHelper = null;

        super.vfunc_style_changed();
    }

    vfunc_paint() {
        if (this._shadowHelper) {
            this._shadowHelper.update(this._drawingArea);

            let allocation = this._drawingArea.get_allocation_box();
            let paintOpacity = this._drawingArea.get_paint_opacity();
            let framebuffer = Cogl.get_draw_framebuffer();

            this._shadowHelper.paint(framebuffer, allocation, paintOpacity);
        }

        this._drawingArea.paint();
    }
});

function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

/**
 * If you are setting org.gnome.desktop.session.idle-delay directly in dconf,
 * rather than through System Settings, you also need to set
 * org.gnome.settings-daemon.plugins.power.sleep-display-ac and
 * org.gnome.settings-daemon.plugins.power.sleep-display-battery to the same value.
 * This will ensure that the screen blanks at the right time when it fades out.
 * https://bugzilla.gnome.org/show_bug.cgi?id=668703 explains the dependency.
 */
var ScreenShield = class {
    constructor() {
        this.actor = Main.layoutManager.screenShieldGroup;

        this._lockScreenState = MessageTray.State.HIDDEN;
        this._lockScreenGroup = new St.Widget({ x_expand: true,
                                                y_expand: true,
                                                reactive: true,
                                                can_focus: true,
                                                name: 'lockScreenGroup',
                                                visible: false,
                                              });
        this._lockScreenGroup.connect('key-press-event',
                                      this._onLockScreenKeyPress.bind(this));
        this._lockScreenGroup.connect('scroll-event',
                                      this._onLockScreenScroll.bind(this));
        Main.ctrlAltTabManager.addGroup(this._lockScreenGroup, _("Lock"), 'changes-prevent-symbolic');

        this._lockScreenContents = new St.Widget({ layout_manager: new Clutter.BinLayout(),
                                                   name: 'lockScreenContents' });
        this._lockScreenContents.add_constraint(new Layout.MonitorConstraint({ primary: true }));

        this._lockScreenGroup.add_actor(this._lockScreenContents);

        this._backgroundGroup = new Clutter.Actor();

        this._lockScreenGroup.add_actor(this._backgroundGroup);
        this._backgroundGroup.lower_bottom();
        this._bgManagers = [];

        this._updateBackgrounds();
        Main.layoutManager.connect('monitors-changed', this._updateBackgrounds.bind(this));

        this._arrowAnimationId = 0;
        this._arrowWatchId = 0;
        this._arrowActiveWatchId = 0;
        this._arrowContainer = new St.BoxLayout({ style_class: 'screen-shield-arrows',
                                                  vertical: true,
                                                  x_align: Clutter.ActorAlign.CENTER,
                                                  y_align: Clutter.ActorAlign.END,
                                                  // HACK: without these, ClutterBinLayout
                                                  // ignores alignment properties on the actor
                                                  x_expand: true,
                                                  y_expand: true });

        for (let i = 0; i < N_ARROWS; i++) {
            let arrow = new Arrow({ opacity: 0 });
            this._arrowContainer.add_actor(arrow);
        }
        this._lockScreenContents.add_actor(this._arrowContainer);

        this._dragAction = new Clutter.GestureAction();
        this._dragAction.connect('gesture-begin', this._onDragBegin.bind(this));
        this._dragAction.connect('gesture-progress', this._onDragMotion.bind(this));
        this._dragAction.connect('gesture-end', this._onDragEnd.bind(this));
        this._lockScreenGroup.add_action(this._dragAction);

        this._lockDialogGroup = new St.Widget({ x_expand: true,
                                                y_expand: true,
                                                reactive: true,
                                                pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
                                                name: 'lockDialogGroup' });

        this.actor.add_actor(this._lockDialogGroup);
        this.actor.add_actor(this._lockScreenGroup);

        this._presence = new GnomeSession.Presence((proxy, error) => {
            if (error) {
                logError(error, 'Error while reading gnome-session presence');
                return;
            }

            this._onStatusChanged(proxy.status);
        });
        this._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => {
            this._onStatusChanged(status);
        });

        this._screenSaverDBus = new ShellDBus.ScreenSaverDBus(this);

        this._smartcardManager = SmartcardManager.getSmartcardManager();
        this._smartcardManager.connect('smartcard-inserted',
                                       (manager, token) => {
                                           if (this._isLocked && token.UsedToLogin) {
                                               this._wakeUpScreen();
                                               this._liftShield(true, 0);
                                           }
                                       });

        this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager();
        this._oVirtCredentialsManager.connect('user-authenticated',
                                              () => {
                                                  if (this._isLocked)
                                                      this._liftShield(true, 0);
                                              });

        this._loginManager = LoginManager.getLoginManager();
        this._loginManager.connect('prepare-for-sleep',
                                   this._prepareForSleep.bind(this));

        this._loginSession = null;
        this._loginManager.getCurrentSessionProxy(sessionProxy => {
            this._loginSession = sessionProxy;
            this._loginSession.connectSignal('Lock',
                                             () => { this.lock(false); });
            this._loginSession.connectSignal('Unlock',
                                             () => { this.deactivate(false); });
            this._loginSession.connect('g-properties-changed', this._syncInhibitor.bind(this));
            this._syncInhibitor();
        });

        this._settings = new Gio.Settings({ schema_id: SCREENSAVER_SCHEMA });
        this._settings.connect('changed::' + LOCK_ENABLED_KEY, this._syncInhibitor.bind(this));

        this._lockSettings = new Gio.Settings({ schema_id: LOCKDOWN_SCHEMA });
        this._lockSettings.connect('changed::' + DISABLE_LOCK_KEY, this._syncInhibitor.bind(this));

        this._isModal = false;
        this._hasLockScreen = false;
        this._isGreeter = false;
        this._isActive = false;
        this._isLocked = false;
        this._inUnlockAnimation = false;
        this._activationTime = 0;
        this._becameActiveId = 0;
        this._lockTimeoutId = 0;

        // The "long" lightbox is used for the longer (20 seconds) fade from session
        // to idle status, the "short" is used for quickly fading to black when locking
        // manually
        this._longLightbox = new Lightbox.Lightbox(Main.uiGroup,
                                                   { inhibitEvents: true,
                                                     fadeFactor: 1 });
        this._longLightbox.connect('shown', this._onLongLightboxShown.bind(this));
        this._shortLightbox = new Lightbox.Lightbox(Main.uiGroup,
                                                    { inhibitEvents: true,
                                                      fadeFactor: 1 });
        this._shortLightbox.connect('shown', this._onShortLightboxShown.bind(this));

        this.idleMonitor = Meta.IdleMonitor.get_core();
        this._cursorTracker = Meta.CursorTracker.get_for_display(global.display);

        this._syncInhibitor();
    }

    _setActive(active) {
        let prevIsActive = this._isActive;
        this._isActive = active;

        if (prevIsActive != this._isActive)
            this.emit('active-changed');

        if (this._loginSession)
            this._loginSession.SetLockedHintRemote(active);

        this._syncInhibitor();
    }

    _createBackground(monitorIndex) {
        let monitor = Main.layoutManager.monitors[monitorIndex];
        let widget = new St.Widget({ style_class: 'screen-shield-background',
                                     x: monitor.x,
                                     y: monitor.y,
                                     width: monitor.width,
                                     height: monitor.height });

        let bgManager = new Background.BackgroundManager({ container: widget,
                                                           monitorIndex: monitorIndex,
                                                           controlPosition: false,
                                                           settingsSchema: SCREENSAVER_SCHEMA });

        this._bgManagers.push(bgManager);

        this._backgroundGroup.add_child(widget);
    }

    _updateBackgrounds() {
        for (let i = 0; i < this._bgManagers.length; i++)
            this._bgManagers[i].destroy();

        this._bgManagers = [];
        this._backgroundGroup.destroy_all_children();

        for (let i = 0; i < Main.layoutManager.monitors.length; i++)
            this._createBackground(i);
    }

    _liftShield(onPrimary, velocity) {
        if (this._isLocked) {
            if (this._ensureUnlockDialog(onPrimary, true /* allowCancel */))
                this._hideLockScreen(true /* animate */, velocity);
        } else {
            this.deactivate(true /* animate */);
        }
    }

    _maybeCancelDialog() {
        if (!this._dialog)
            return;

        this._dialog.cancel();
        if (this._isGreeter) {
            // LoginDialog.cancel() will grab the key focus
            // on its own, so ensure it stays on lock screen
            // instead
            this._lockScreenGroup.grab_key_focus();
        } else {
            this._dialog = null;
        }
    }

    _becomeModal() {
        if (this._isModal)
            return true;

        this._isModal = Main.pushModal(this.actor, { actionMode: Shell.ActionMode.LOCK_SCREEN });
        if (this._isModal)
            return true;

        // We failed to get a pointer grab, it means that
        // something else has it. Try with a keyboard grab only
        this._isModal = Main.pushModal(this.actor, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED,
                                                     actionMode: Shell.ActionMode.LOCK_SCREEN });
        return this._isModal;
    }

    _onLockScreenKeyPress(actor, event) {
        let symbol = event.get_key_symbol();
        let unichar = event.get_key_unicode();

        // Do nothing if the lock screen is not fully shown.
        // This avoids reusing the previous (and stale) unlock
        // dialog if esc is pressed while the curtain is going
        // down after cancel.

        if (this._lockScreenState != MessageTray.State.SHOWN)
            return Clutter.EVENT_PROPAGATE;

        let isEnter = (symbol == Clutter.KEY_Return ||
                       symbol == Clutter.KEY_KP_Enter ||
                       symbol == Clutter.KEY_ISO_Enter);
        let isEscape = (symbol == Clutter.KEY_Escape);
        let isLiftChar = (GLib.unichar_isprint(unichar) &&
                          (this._isLocked || !GLib.unichar_isgraph(unichar)));
        if (!isEnter && !isEscape && !isLiftChar)
            return Clutter.EVENT_PROPAGATE;

        if (this._isLocked &&
            this._ensureUnlockDialog(true, true) &&
            GLib.unichar_isgraph(unichar))
            this._dialog.addCharacter(unichar);

        this._liftShield(true, 0);
        return Clutter.EVENT_STOP;
    }

    _onLockScreenScroll(actor, event) {
        if (this._lockScreenState != MessageTray.State.SHOWN)
            return Clutter.EVENT_PROPAGATE;

        let delta = 0;
        if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
            delta = Math.abs(event.get_scroll_delta()[0]);
        else
            delta = 5;

        this._lockScreenScrollCounter += delta;

        // 7 standard scrolls to lift up
        if (this._lockScreenScrollCounter > 35) {
            this._liftShield(true, 0);
        }

        return Clutter.EVENT_STOP;
    }

    _syncInhibitor() {
        let lockEnabled = this._settings.get_boolean(LOCK_ENABLED_KEY);
        let lockLocked = this._lockSettings.get_boolean(DISABLE_LOCK_KEY);
        let inhibit = (this._loginSession && this._loginSession.Active &&
                       !this._isActive && lockEnabled && !lockLocked);
        if (inhibit) {
            this._loginManager.inhibit(_("GNOME needs to lock the screen"),
                inhibitor => {
                    if (this._inhibitor)
                        this._inhibitor.close(null);
                    this._inhibitor = inhibitor;
                });
        } else {
            if (this._inhibitor)
                this._inhibitor.close(null);
            this._inhibitor = null;
        }
    }

    _prepareForSleep(loginManager, aboutToSuspend) {
        if (aboutToSuspend) {
            if (this._settings.get_boolean(LOCK_ENABLED_KEY))
                this.lock(true);
        } else {
            this._wakeUpScreen();
        }
    }

    _animateArrows() {
        let arrows = this._arrowContainer.get_children();
        let unitaryDelay = ARROW_ANIMATION_TIME / (arrows.length + 1);
        let maxOpacity = 255 * ARROW_ANIMATION_PEAK_OPACITY;
        for (let i = 0; i < arrows.length; i++) {
            arrows[i].opacity = 0;
            Tweener.addTween(arrows[i],
                             { opacity: 0,
                               delay: unitaryDelay * (N_ARROWS - (i + 1)),
                               time: ARROW_ANIMATION_TIME,
                               transition(t, b, c, d) {
                                 if (t < d/2)
                                     return TweenerEquations.easeOutQuad(t, 0, maxOpacity, d/2);
                                 else
                                     return TweenerEquations.easeInQuad(t - d/2, maxOpacity, -maxOpacity, d/2);
                               }
                             });
        }

        return GLib.SOURCE_CONTINUE;
    }

    _onDragBegin() {
        Tweener.removeTweens(this._lockScreenGroup);
        this._lockScreenState = MessageTray.State.HIDING;

        if (this._isLocked)
            this._ensureUnlockDialog(false, false);

        return true;
    }

    _onDragMotion() {
	let [origX, origY] = this._dragAction.get_press_coords(0);
	let [currentX, currentY] = this._dragAction.get_motion_coords(0);

	let newY = currentY - origY;
	newY = clamp(newY, -global.stage.height, 0);

	this._lockScreenGroup.y = newY;

	return true;
    }

    _onDragEnd(action, actor, eventX, eventY, modifiers) {
        if (this._lockScreenState != MessageTray.State.HIDING)
            return;
        if (this._lockScreenGroup.y < -(ARROW_DRAG_THRESHOLD * global.stage.height)) {
            // Complete motion automatically
	    let [velocity, velocityX, velocityY] = this._dragAction.get_velocity(0);
            this._liftShield(true, -velocityY);
        } else {
            // restore the lock screen to its original place
            // try to use the same speed as the normal animation
            let h = global.stage.height;
            let time = MANUAL_FADE_TIME * (-this._lockScreenGroup.y) / h;
            Tweener.removeTweens(this._lockScreenGroup);
            Tweener.addTween(this._lockScreenGroup,
                             { y: 0,
                               time: time,
                               transition: 'easeInQuad',
                               onComplete() {
                                   this._lockScreenGroup.fixed_position_set = false;
                                   this._lockScreenState = MessageTray.State.SHOWN;
                               },
                               onCompleteScope: this,
                             });

            this._maybeCancelDialog();
        }
    }

    _onStatusChanged(status) {
        if (status != GnomeSession.PresenceStatus.IDLE)
            return;

        this._maybeCancelDialog();

        if (this._longLightbox.actor.visible) {
            // We're in the process of showing.
            return;
        }

        if (!this._becomeModal()) {
            // We could not become modal, so we can't activate the
            // screenshield. The user is probably very upset at this
            // point, but any application using global grabs is broken
            // Just tell him to stop using this app
            //
            // XXX: another option is to kick the user into the gdm login
            // screen, where we're not affected by grabs
            Main.notifyError(_("Unable to lock"),
                             _("Lock was blocked by an application"));
            return;
        }

        if (this._activationTime == 0)
            this._activationTime = GLib.get_monotonic_time();

        let shouldLock = this._settings.get_boolean(LOCK_ENABLED_KEY) && !this._isLocked;

        if (shouldLock) {
            let lockTimeout = Math.max(STANDARD_FADE_TIME, this._settings.get_uint(LOCK_DELAY_KEY));
            this._lockTimeoutId = Mainloop.timeout_add(lockTimeout * 1000,
                                                       () => {
                                                           this._lockTimeoutId = 0;
                                                           this.lock(false);
                                                           return GLib.SOURCE_REMOVE;
                                                       });
            GLib.Source.set_name_by_id(this._lockTimeoutId, '[gnome-shell] this.lock');
        }

        this._activateFade(this._longLightbox, STANDARD_FADE_TIME);
    }

    _activateFade(lightbox, time) {
        Main.uiGroup.set_child_above_sibling(lightbox.actor, null);
        lightbox.show(time);

        if (this._becameActiveId == 0)
            this._becameActiveId = this.idleMonitor.add_user_active_watch(this._onUserBecameActive.bind(this));
    }

    _onUserBecameActive() {
        // This function gets called here when the user becomes active
        // after we activated a lightbox
        // There are two possibilities here:
        // - we're called when already locked/active; isLocked or isActive is true,
        //   we just go back to the lock screen curtain
        //   (isActive == isLocked == true: normal case
        //    isActive == false, isLocked == true: during the fade for manual locking
        //    isActive == true, isLocked == false: after session idle, before lock-delay)
        // - we're called because the session is IDLE but before the lightbox
        //   is fully shown; at this point isActive is false, so we just hide
        //   the lightbox, reset the activationTime and go back to the unlocked
        //   desktop
        //   using deactivate() is a little of overkill, but it ensures we
        //   don't forget of some bit like modal, DBus properties or idle watches
        //
        // Note: if the (long) lightbox is shown then we're necessarily
        // active, because we call activate() without animation.

        this.idleMonitor.remove_watch(this._becameActiveId);
        this._becameActiveId = 0;

        if (this._isActive || this._isLocked) {
            this._longLightbox.hide();
            this._shortLightbox.hide();
        } else {
            this.deactivate(false);
        }
    }

    _onLongLightboxShown() {
        this.activate(false);
    }

    _onShortLightboxShown() {
        this._completeLockScreenShown();
    }

    showDialog() {
        if (!this._becomeModal()) {
            // In the login screen, this is a hard error. Fail-whale
            log('Could not acquire modal grab for the login screen. Aborting login process.');
            Meta.quit(Meta.ExitCode.ERROR);
        }

        this.actor.show();
        this._isGreeter = Main.sessionMode.isGreeter;
        this._isLocked = true;
        if (this._ensureUnlockDialog(true, true))
            this._hideLockScreen(false, 0);
    }

    _hideLockScreenComplete() {
        if (Main.sessionMode.currentMode == 'lock-screen')
            Main.sessionMode.popMode('lock-screen');

        this._lockScreenState = MessageTray.State.HIDDEN;
        this._lockScreenGroup.hide();

        if (this._dialog) {
            this._dialog.grab_key_focus();
            this._dialog.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
        }
    }

    _hideLockScreen(animate, velocity) {
        if (this._lockScreenState == MessageTray.State.HIDDEN)
            return;

        this._lockScreenState = MessageTray.State.HIDING;

        Tweener.removeTweens(this._lockScreenGroup);

        if (animate) {
            // Tween the lock screen out of screen
            // if velocity is not specified (i.e. we come here from pressing ESC),
            // use the same speed regardless of original position
            // if velocity is specified, it's in pixels per milliseconds
            let h = global.stage.height;
            let delta = (h + this._lockScreenGroup.y);
            let min_velocity = global.stage.height / (CURTAIN_SLIDE_TIME * 1000);

            velocity = Math.max(min_velocity, velocity);
            let time = (delta / velocity) / 1000;

            Tweener.addTween(this._lockScreenGroup,
                             { y: -h,
                               time: time,
                               transition: 'easeInQuad',
                               onComplete: this._hideLockScreenComplete.bind(this),
                             });
        } else {
            this._hideLockScreenComplete();
        }

        this._cursorTracker.set_pointer_visible(true);
    }

    _ensureUnlockDialog(onPrimary, allowCancel) {
        if (!this._dialog) {
            let constructor = Main.sessionMode.unlockDialog;
            if (!constructor) {
                // This session mode has no locking capabilities
                this.deactivate(true);
                return false;
            }

            this._dialog = new constructor(this._lockDialogGroup);


            let time = global.get_current_time();
            if (!this._dialog.open(time, onPrimary)) {
                // This is kind of an impossible error: we're already modal
                // by the time we reach this...
                log('Could not open login dialog: failed to acquire grab');
                this.deactivate(true);
                return false;
            }

            this._dialog.connect('failed', this._onUnlockFailed.bind(this));
        }

        this._dialog.allowCancel = allowCancel;
        return true;
    }

    _onUnlockFailed() {
        this._resetLockScreen({ animateLockScreen: true,
                                fadeToBlack: false });
    }

    _resetLockScreen(params) {
        // Don't reset the lock screen unless it is completely hidden
        // This prevents the shield going down if the lock-delay timeout
        // fires while the user is dragging (which has the potential
        // to confuse our state)
        if (this._lockScreenState != MessageTray.State.HIDDEN)
            return;

        this._ensureLockScreen();
        this._lockDialogGroup.scale_x = 1;
        this._lockDialogGroup.scale_y = 1;

        this._lockScreenGroup.show();
        this._lockScreenState = MessageTray.State.SHOWING;

        let fadeToBlack = params.fadeToBlack;

        if (params.animateLockScreen) {
            this._lockScreenGroup.y = -global.screen_height;
            Tweener.removeTweens(this._lockScreenGroup);
            Tweener.addTween(this._lockScreenGroup,
                             { y: 0,
                               time: MANUAL_FADE_TIME,
                               transition: 'easeOutQuad',
                               onComplete() {
                                   this._lockScreenShown({ fadeToBlack: fadeToBlack,
                                                           animateFade: true });
                               },
                               onCompleteScope: this
                             });
        } else {
            this._lockScreenGroup.fixed_position_set = false;
            this._lockScreenShown({ fadeToBlack: fadeToBlack,
                                    animateFade: false });
        }

        this._lockScreenGroup.grab_key_focus();

        if (Main.sessionMode.currentMode != 'lock-screen')
            Main.sessionMode.pushMode('lock-screen');
    }

    _startArrowAnimation() {
        this._arrowActiveWatchId = 0;

        if (!this._arrowAnimationId) {
            this._arrowAnimationId = Mainloop.timeout_add(6000, this._animateArrows.bind(this));
            GLib.Source.set_name_by_id(this._arrowAnimationId, '[gnome-shell] this._animateArrows');
            this._animateArrows();
        }

        if (!this._arrowWatchId)
            this._arrowWatchId = this.idleMonitor.add_idle_watch(ARROW_IDLE_TIME,
                                                                 this._pauseArrowAnimation.bind(this));
    }

    _pauseArrowAnimation() {
        if (this._arrowAnimationId) {
            Mainloop.source_remove(this._arrowAnimationId);
            this._arrowAnimationId = 0;
        }

        if (!this._arrowActiveWatchId)
            this._arrowActiveWatchId = this.idleMonitor.add_user_active_watch(this._startArrowAnimation.bind(this));
    }

    _stopArrowAnimation() {
        if (this._arrowAnimationId) {
            Mainloop.source_remove(this._arrowAnimationId);
            this._arrowAnimationId = 0;
        }
        if (this._arrowActiveWatchId) {
            this.idleMonitor.remove_watch(this._arrowActiveWatchId);
            this._arrowActiveWatchId = 0;
        }
        if (this._arrowWatchId) {
            this.idleMonitor.remove_watch(this._arrowWatchId);
            this._arrowWatchId = 0;
        }
    }

    _checkArrowAnimation() {
        let idleTime = this.idleMonitor.get_idletime();

        if (idleTime < ARROW_IDLE_TIME)
            this._startArrowAnimation();
        else
            this._pauseArrowAnimation();
    }

    _lockScreenShown(params) {
        if (this._dialog && !this._isGreeter) {
            this._dialog.destroy();
            this._dialog = null;
        }

        this._checkArrowAnimation();

        let motionId = global.stage.connect('captured-event', (stage, event) => {
            if (event.type() == Clutter.EventType.MOTION) {
                this._cursorTracker.set_pointer_visible(true);
                global.stage.disconnect(motionId);
            }

            return Clutter.EVENT_PROPAGATE;
        });
        this._cursorTracker.set_pointer_visible(false);

        this._lockScreenState = MessageTray.State.SHOWN;
        this._lockScreenGroup.fixed_position_set = false;
        this._lockScreenScrollCounter = 0;

        if (params.fadeToBlack && params.animateFade) {
            // Take a beat

            let id = Mainloop.timeout_add(1000 * MANUAL_FADE_TIME, () => {
                this._activateFade(this._shortLightbox, MANUAL_FADE_TIME);
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(id, '[gnome-shell] this._activateFade');
        } else {
            if (params.fadeToBlack)
                this._activateFade(this._shortLightbox, 0);

            this._completeLockScreenShown();
        }
    }

    _completeLockScreenShown() {
        this._setActive(true);
        this.emit('lock-screen-shown');
    }

    // Some of the actors in the lock screen are heavy in
    // resources, so we only create them when needed
    _ensureLockScreen() {
        if (this._hasLockScreen)
            return;

        this._lockScreenContentsBox = new St.BoxLayout({ x_align: Clutter.ActorAlign.CENTER,
                                                         y_align: Clutter.ActorAlign.CENTER,
                                                         x_expand: true,
                                                         y_expand: true,
                                                         vertical: true,
                                                         style_class: 'screen-shield-contents-box' });
        this._clock = new Clock();
        this._lockScreenContentsBox.add(this._clock.actor, { x_fill: true,
                                                             y_fill: true });

        this._lockScreenContents.add_actor(this._lockScreenContentsBox);

        this._notificationsBox = new NotificationsBox();
        this._wakeUpScreenId = this._notificationsBox.connect('wake-up-screen', this._wakeUpScreen.bind(this));
        this._lockScreenContentsBox.add(this._notificationsBox.actor, { x_fill: true,
                                                                        y_fill: true,
                                                                        expand: true });

        this._hasLockScreen = true;
    }

    _wakeUpScreen() {
        this._onUserBecameActive();
        this.emit('wake-up-screen');
    }

    _clearLockScreen() {
        this._clock.destroy();
        this._clock = null;

        if (this._notificationsBox) {
            this._notificationsBox.disconnect(this._wakeUpScreenId);
            this._notificationsBox.destroy();
            this._notificationsBox = null;
        }

        this._stopArrowAnimation();

        this._lockScreenContentsBox.destroy();

        this._hasLockScreen = false;
    }

    get locked() {
        return this._isLocked;
    }

    get active() {
        return this._isActive;
    }

    get activationTime() {
        return this._activationTime;
    }

    deactivate(animate) {
        if (this._dialog)
            this._dialog.finish(() => { this._continueDeactivate(animate); });
        else
            this._continueDeactivate(animate);
    }

    _continueDeactivate(animate) {
        this._hideLockScreen(animate, 0);

        if (this._hasLockScreen)
            this._clearLockScreen();

        if (Main.sessionMode.currentMode == 'lock-screen')
            Main.sessionMode.popMode('lock-screen');
        if (Main.sessionMode.currentMode == 'unlock-dialog')
            Main.sessionMode.popMode('unlock-dialog');

        this.emit('wake-up-screen');

        if (this._isGreeter) {
            // We don't want to "deactivate" any more than
            // this. In particular, we don't want to drop
            // the modal, hide ourselves or destroy the dialog
            // But we do want to set isActive to false, so that
            // gnome-session will reset the idle counter, and
            // gnome-settings-daemon will stop blanking the screen

            this._activationTime = 0;
            this._setActive(false);
            return;
        }

        if (this._dialog && !this._isGreeter)
            this._dialog.popModal();

        if (this._isModal) {
            Main.popModal(this.actor);
            this._isModal = false;
        }

        this._longLightbox.hide();
        this._shortLightbox.hide();

        Tweener.addTween(this._lockDialogGroup, {
            scale_x: 0,
            scale_y: 0,
            time: animate ? Overview.ANIMATION_TIME : 0,
            transition: 'easeOutQuad',
            onComplete: this._completeDeactivate.bind(this),
            onCompleteScope: this
        });
    }

    _completeDeactivate() {
        if (this._dialog) {
            this._dialog.destroy();
            this._dialog = null;
        }

        this.actor.hide();

        if (this._becameActiveId != 0) {
            this.idleMonitor.remove_watch(this._becameActiveId);
            this._becameActiveId = 0;
        }

        if (this._lockTimeoutId != 0) {
            Mainloop.source_remove(this._lockTimeoutId);
            this._lockTimeoutId = 0;
        }

        this._activationTime = 0;
        this._setActive(false);
        this._isLocked = false;
        this.emit('locked-changed');
        global.set_runtime_state(LOCKED_STATE_STR, null);
    }

    activate(animate) {
        if (this._activationTime == 0)
            this._activationTime = GLib.get_monotonic_time();

        this.actor.show();

        if (Main.sessionMode.currentMode != 'unlock-dialog' &&
            Main.sessionMode.currentMode != 'lock-screen') {
            this._isGreeter = Main.sessionMode.isGreeter;
            if (!this._isGreeter)
                Main.sessionMode.pushMode('unlock-dialog');
        }

        this._resetLockScreen({ animateLockScreen: animate,
                                fadeToBlack: true });
        // On wayland, a crash brings down the entire session, so we don't
        // need to defend against being restarted unlocked
        if (!Meta.is_wayland_compositor())
            global.set_runtime_state(LOCKED_STATE_STR, GLib.Variant.new('b', true));

        // We used to set isActive and emit active-changed here,
        // but now we do that from lockScreenShown, which means
        // there is a 0.3 seconds window during which the lock
        // screen is effectively visible and the screen is locked, but
        // the DBus interface reports the screensaver is off.
        // This is because when we emit ActiveChanged(true),
        // gnome-settings-daemon blanks the screen, and we don't want
        // blank during the animation.
        // This is not a problem for the idle fade case, because we
        // activate without animation in that case.
    }

    lock(animate) {
        if (this._lockSettings.get_boolean(DISABLE_LOCK_KEY)) {
            log('Screen lock is locked down, not locking') // lock, lock - who's there?
            return;
        }

        // Warn the user if we can't become modal
        if (!this._becomeModal()) {
            Main.notifyError(_("Unable to lock"),
                             _("Lock was blocked by an application"));
            return;
        }

        // Clear the clipboard - otherwise, its contents may be leaked
        // to unauthorized parties by pasting into the unlock dialog's
        // password entry and unmasking the entry
        St.Clipboard.get_default().set_text(St.ClipboardType.CLIPBOARD, '');
        St.Clipboard.get_default().set_text(St.ClipboardType.PRIMARY, '');

        let userManager = AccountsService.UserManager.get_default();
        let user = userManager.get_user(GLib.get_user_name());

        if (this._isGreeter)
            this._isLocked = true;
        else
            this._isLocked = user.password_mode != AccountsService.UserPasswordMode.NONE;

        this.activate(animate);

        this.emit('locked-changed');
    }

    // If the previous shell crashed, and gnome-session restarted us, then re-lock
    lockIfWasLocked() {
        if (!this._settings.get_boolean(LOCK_ENABLED_KEY))
            return;
        let wasLocked = global.get_runtime_state('b', LOCKED_STATE_STR);
        if (wasLocked === null)
            return;
        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            this.lock(false);
        });
    }
};
Signals.addSignalMethods(ScreenShield.prototype);
(uuay)gdm/AO\tFibusCandidatePopup.js�.// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, IBus, St } = imports.gi;
const Signals = imports.signals;

const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;

var MAX_CANDIDATES_PER_PAGE = 16;

var DEFAULT_INDEX_LABELS = [ '1', '2', '3', '4', '5', '6', '7', '8',
                             '9', '0', 'a', 'b', 'c', 'd', 'e', 'f' ];

var CandidateArea = class CandidateArea {
    constructor() {
        this.actor = new St.BoxLayout({ vertical: true,
                                        reactive: true,
                                        visible: false });
        this._candidateBoxes = [];
        for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
            let box = new St.BoxLayout({ style_class: 'candidate-box',
                                         reactive: true,
                                         track_hover: true });
            box._indexLabel = new St.Label({ style_class: 'candidate-index' });
            box._candidateLabel = new St.Label({ style_class: 'candidate-label' });
            box.add(box._indexLabel, { y_fill: false });
            box.add(box._candidateLabel, { y_fill: false });
            this._candidateBoxes.push(box);
            this.actor.add(box);

            let j = i;
            box.connect('button-release-event', (actor, event) => {
                this.emit('candidate-clicked', j, event.get_button(), event.get_state());
                return Clutter.EVENT_PROPAGATE;
            });
        }

        this.actor.connect('scroll-event', (actor, event) => {
            let direction = event.get_scroll_direction();
            switch(direction) {
            case Clutter.ScrollDirection.UP:
                this.emit('cursor-up');
                break;
            case Clutter.ScrollDirection.DOWN:
                this.emit('cursor-down');
                break;
            };
            return Clutter.EVENT_PROPAGATE;
        });

        this._buttonBox = new St.BoxLayout({ style_class: 'candidate-page-button-box' });

        this._previousButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-previous button' });
        this._previousButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
        this._buttonBox.add(this._previousButton, { expand: true });

        this._nextButton = new St.Button({ style_class: 'candidate-page-button candidate-page-button-next button' });
        this._nextButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
        this._buttonBox.add(this._nextButton, { expand: true });

        this.actor.add(this._buttonBox);

        this._previousButton.connect('clicked', () => {
            this.emit('previous-page');
        });
        this._nextButton.connect('clicked', () => {
            this.emit('next-page');
        });

        this._orientation = -1;
        this._cursorPosition = 0;
    }

    setOrientation(orientation) {
        if (this._orientation == orientation)
            return;

        this._orientation = orientation;

        if (this._orientation == IBus.Orientation.HORIZONTAL) {
            this.actor.vertical = false;
            this.actor.remove_style_class_name('vertical');
            this.actor.add_style_class_name('horizontal');
            this._previousButton.child.icon_name = 'go-previous-symbolic';
            this._nextButton.child.icon_name = 'go-next-symbolic';
        } else {                // VERTICAL || SYSTEM
            this.actor.vertical = true;
            this.actor.add_style_class_name('vertical');
            this.actor.remove_style_class_name('horizontal');
            this._previousButton.child.icon_name = 'go-up-symbolic';
            this._nextButton.child.icon_name = 'go-down-symbolic';
        }
    }

    setCandidates(indexes, candidates, cursorPosition, cursorVisible) {
        for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
            let visible = i < candidates.length;
            let box = this._candidateBoxes[i];
            box.visible = visible;

            if (!visible)
                continue;

            box._indexLabel.text = ((indexes && indexes[i]) ? indexes[i] : DEFAULT_INDEX_LABELS[i]);
            box._candidateLabel.text = candidates[i];
        }

        this._candidateBoxes[this._cursorPosition].remove_style_pseudo_class('selected');
        this._cursorPosition = cursorPosition;
        if (cursorVisible)
            this._candidateBoxes[cursorPosition].add_style_pseudo_class('selected');
    }

    updateButtons(wrapsAround, page, nPages) {
        if (nPages < 2) {
            this._buttonBox.hide();
            return;
        }
        this._buttonBox.show();
        this._previousButton.reactive = wrapsAround || page > 0;
        this._nextButton.reactive = wrapsAround || page < nPages - 1;
    }
};
Signals.addSignalMethods(CandidateArea.prototype);

var CandidatePopup = class CandidatePopup {
    constructor() {
        this._boxPointer = new BoxPointer.BoxPointer(St.Side.TOP);
        this._boxPointer.visible = false;
        this._boxPointer.style_class = 'candidate-popup-boxpointer';
        Main.layoutManager.addChrome(this._boxPointer);

        let box = new St.BoxLayout({ style_class: 'candidate-popup-content',
                                     vertical: true });
        this._boxPointer.bin.set_child(box);

        this._preeditText = new St.Label({ style_class: 'candidate-popup-text',
                                           visible: false });
        box.add(this._preeditText);

        this._auxText = new St.Label({ style_class: 'candidate-popup-text',
                                       visible: false });
        box.add(this._auxText);

        this._candidateArea = new CandidateArea();
        box.add(this._candidateArea.actor);

        this._candidateArea.connect('previous-page', () => {
            this._panelService.page_up();
        });
        this._candidateArea.connect('next-page', () => {
            this._panelService.page_down();
        });

        this._candidateArea.connect('cursor-up', () => {
            this._panelService.cursor_up();
        });
        this._candidateArea.connect('cursor-down', () => {
            this._panelService.cursor_down();
        });

        this._candidateArea.connect('candidate-clicked', (area, index, button, state) => {
            this._panelService.candidate_clicked(index, button, state);
        });

        this._panelService = null;
    }

    setPanelService(panelService) {
        this._panelService = panelService;
        if (!panelService)
            return;

        panelService.connect('set-cursor-location', (ps, x, y, w, h) => {
            this._setDummyCursorGeometry(x, y, w, h);
        });
        try {
            panelService.connect('set-cursor-location-relative', (ps, x, y, w, h) => {
                if (!global.display.focus_window)
                    return;
                let window = global.display.focus_window.get_compositor_private();
                this._setDummyCursorGeometry(window.x + x, window.y + y, w, h);
            });
        } catch(e) {
            // Only recent IBus versions have support for this signal
            // which is used for wayland clients. In order to work
            // with older IBus versions we can silently ignore the
            // signal's absence.
        }
        panelService.connect('update-preedit-text', (ps, text, cursorPosition, visible) => {
            this._preeditText.visible = visible;
            this._updateVisibility();

            this._preeditText.text = text.get_text();

            let attrs = text.get_attributes();
            if (attrs)
                this._setTextAttributes(this._preeditText.clutter_text,
                                        attrs);
        });
        panelService.connect('show-preedit-text', ps => {
            this._preeditText.show();
            this._updateVisibility();
        });
        panelService.connect('hide-preedit-text', ps => {
            this._preeditText.hide();
            this._updateVisibility();
        });
        panelService.connect('update-auxiliary-text', (ps, text, visible) => {
            this._auxText.visible = visible;
            this._updateVisibility();

            this._auxText.text = text.get_text();
        });
        panelService.connect('show-auxiliary-text', ps => {
            this._auxText.show();
            this._updateVisibility();
        });
        panelService.connect('hide-auxiliary-text', ps => {
            this._auxText.hide();
            this._updateVisibility();
        });
        panelService.connect('update-lookup-table', (ps, lookupTable, visible) => {
            this._candidateArea.actor.visible = visible;
            this._updateVisibility();

            let nCandidates = lookupTable.get_number_of_candidates();
            let cursorPos = lookupTable.get_cursor_pos();
            let pageSize = lookupTable.get_page_size();
            let nPages = Math.ceil(nCandidates / pageSize);
            let page = ((cursorPos == 0) ? 0 : Math.floor(cursorPos / pageSize));
            let startIndex = page * pageSize;
            let endIndex = Math.min((page + 1) * pageSize, nCandidates);

            let indexes = [];
            let indexLabel;
            for (let i = 0; (indexLabel = lookupTable.get_label(i)); ++i)
                 indexes.push(indexLabel.get_text());

            Main.keyboard.resetSuggestions();

            let candidates = [];
            for (let i = startIndex; i < endIndex; ++i) {
                candidates.push(lookupTable.get_candidate(i).get_text());

                Main.keyboard.addSuggestion(lookupTable.get_candidate(i).get_text(), () => {
                    let index = i;
                    this._panelService.candidate_clicked(index, 1, 0);
                });
            }

            this._candidateArea.setCandidates(indexes,
                                              candidates,
                                              cursorPos % pageSize,
                                              lookupTable.is_cursor_visible());
            this._candidateArea.setOrientation(lookupTable.get_orientation());
            this._candidateArea.updateButtons(lookupTable.is_round(), page, nPages);
        });
        panelService.connect('show-lookup-table', ps => {
            this._candidateArea.actor.show();
            this._updateVisibility();
        });
        panelService.connect('hide-lookup-table', ps => {
            this._candidateArea.actor.hide();
            this._updateVisibility();
        });
        panelService.connect('focus-out', ps => {
            this._boxPointer.close(BoxPointer.PopupAnimation.NONE);
            Main.keyboard.resetSuggestions();
        });
    }

    _setDummyCursorGeometry(x, y, w, h) {
        Main.layoutManager.setDummyCursorGeometry(x, y, w, h);
        if (this._boxPointer.actor.visible)
            this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0);
    }

    _updateVisibility() {
        let isVisible = (!Main.keyboard.visible &&
                         (this._preeditText.visible ||
                          this._auxText.visible ||
                          this._candidateArea.actor.visible));

        if (isVisible) {
            this._boxPointer.setPosition(Main.layoutManager.dummyCursor, 0);
            this._boxPointer.open(BoxPointer.PopupAnimation.NONE);
            this._boxPointer.actor.raise_top();
        } else {
            this._boxPointer.close(BoxPointer.PopupAnimation.NONE);
        }
    }

    _setTextAttributes(clutterText, ibusAttrList) {
        let attr;
        for (let i = 0; (attr = ibusAttrList.get(i)); ++i)
            if (attr.get_attr_type() == IBus.AttrType.BACKGROUND)
                clutterText.set_selection(attr.get_start_index(), attr.get_end_index());
    }
};
(uuay)util.js�S// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib } = imports.gi;
const Signals = imports.signals;

const Batch = imports.gdm.batch;
const Fprint = imports.gdm.fingerprint;
const OVirt = imports.gdm.oVirt;
const Main = imports.ui.main;
const Params = imports.misc.params;
const SmartcardManager = imports.misc.smartcardManager;
const Tweener = imports.ui.tweener;

var PASSWORD_SERVICE_NAME = 'gdm-password';
var FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
var SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
var OVIRT_SERVICE_NAME = 'gdm-ovirtcred';
var FADE_ANIMATION_TIME = 0.16;
var CLONE_FADE_ANIMATION_TIME = 0.25;

var LOGIN_SCREEN_SCHEMA = 'org.gnome.login-screen';
var PASSWORD_AUTHENTICATION_KEY = 'enable-password-authentication';
var FINGERPRINT_AUTHENTICATION_KEY = 'enable-fingerprint-authentication';
var SMARTCARD_AUTHENTICATION_KEY = 'enable-smartcard-authentication';
var BANNER_MESSAGE_KEY = 'banner-message-enable';
var BANNER_MESSAGE_TEXT_KEY = 'banner-message-text';
var ALLOWED_FAILURES_KEY = 'allowed-failures';

var LOGO_KEY = 'logo';
var DISABLE_USER_LIST_KEY = 'disable-user-list';

// Give user 48ms to read each character of a PAM message
var USER_READ_TIME = 48

var MessageType = {
    NONE: 0,
    ERROR: 1,
    INFO: 2,
    HINT: 3
};

function fadeInActor(actor) {
    if (actor.opacity == 255 && actor.visible)
        return null;

    let hold = new Batch.Hold();
    actor.show();
    let [minHeight, naturalHeight] = actor.get_preferred_height(-1);

    actor.opacity = 0;
    actor.set_height(0);
    Tweener.addTween(actor,
                     { opacity: 255,
                       height: naturalHeight,
                       time: FADE_ANIMATION_TIME,
                       transition: 'easeOutQuad',
                       onComplete() {
                           this.set_height(-1);
                           hold.release();
                       },
                     });

    return hold;
}

function fadeOutActor(actor) {
    if (!actor.visible || actor.opacity == 0) {
        actor.opacity = 0;
        actor.hide();
        return null;
    }

    let hold = new Batch.Hold();
    Tweener.addTween(actor,
                     { opacity: 0,
                       height: 0,
                       time: FADE_ANIMATION_TIME,
                       transition: 'easeOutQuad',
                       onComplete() {
                           this.hide();
                           this.set_height(-1);
                           hold.release();
                       },
                     });
    return hold;
}

function cloneAndFadeOutActor(actor) {
    // Immediately hide actor so its sibling can have its space
    // and position, but leave a non-reactive clone on-screen,
    // so from the user's point of view it smoothly fades away
    // and reveals its sibling.
    actor.hide();

    let clone = new Clutter.Clone({ source: actor,
                                    reactive: false });

    Main.uiGroup.add_child(clone);

    let [x, y] = actor.get_transformed_position();
    clone.set_position(x, y);

    let hold = new Batch.Hold();
    Tweener.addTween(clone,
                     { opacity: 0,
                       time: CLONE_FADE_ANIMATION_TIME,
                       transition: 'easeOutQuad',
                       onComplete() {
                           clone.destroy();
                           hold.release();
                       }
                     });
    return hold;
}

var ShellUserVerifier = class {
    constructor(client, params) {
        params = Params.parse(params, { reauthenticationOnly: false });
        this._reauthOnly = params.reauthenticationOnly;

        this._client = client;

        this._defaultService = null;
        this._preemptingService = null;

        this._settings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
        this._settings.connect('changed',
                               this._updateDefaultService.bind(this));

        this._fprintManager = Fprint.FprintManager();
        this._smartcardManager = SmartcardManager.getSmartcardManager();

        // We check for smartcards right away, since an inserted smartcard
        // at startup should result in immediately initiating authentication.
        // This is different than fingerprint readers, where we only check them
        // after a user has been picked.
        this.smartcardDetected = false;
        this._checkForSmartcard();

        this._updateDefaultService();

        this._smartcardInsertedId = this._smartcardManager.connect('smartcard-inserted',
                                                                   this._checkForSmartcard.bind(this));
        this._smartcardRemovedId = this._smartcardManager.connect('smartcard-removed',
                                                                  this._checkForSmartcard.bind(this));

        this._messageQueue = [];
        this._messageQueueTimeoutId = 0;
        this.hasPendingMessages = false;
        this.reauthenticating = false;

        this._failCounter = 0;

        this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager();

        if (this._oVirtCredentialsManager.hasToken())
            this._oVirtUserAuthenticated(this._oVirtCredentialsManager.getToken());

        this._oVirtUserAuthenticatedId = this._oVirtCredentialsManager.connect('user-authenticated',
                                                                               this._oVirtUserAuthenticated.bind(this));
    }

    begin(userName, hold) {
        this._cancellable = new Gio.Cancellable();
        this._hold = hold;
        this._userName = userName;
        this.reauthenticating = false;

        this._checkForFingerprintReader();

        if (userName) {
            // If possible, reauthenticate an already running session,
            // so any session specific credentials get updated appropriately
            this._client.open_reauthentication_channel(userName, this._cancellable,
                                                       this._reauthenticationChannelOpened.bind(this));
        } else {
            this._client.get_user_verifier(this._cancellable, this._userVerifierGot.bind(this));
        }
    }

    cancel() {
        if (this._cancellable)
            this._cancellable.cancel();

        if (this._userVerifier) {
            this._userVerifier.call_cancel_sync(null);
            this.clear();
        }
    }

    _clearUserVerifier() {
        if (this._userVerifier) {
            this._userVerifier.run_dispose();
            this._userVerifier = null;
            if (this._userVerifierChoiceList) {
                this._userVerifierChoiceList.run_dispose();
                this._userVerifierChoiceList = null;
            }
        }
    }

    clear() {
        if (this._cancellable) {
            this._cancellable.cancel();
            this._cancellable = null;
        }

        this._clearUserVerifier();
        this._clearMessageQueue();
    }

    destroy() {
        this.clear();

        this._settings.run_dispose();
        this._settings = null;

        this._smartcardManager.disconnect(this._smartcardInsertedId);
        this._smartcardManager.disconnect(this._smartcardRemovedId);
        this._smartcardManager = null;

        this._oVirtCredentialsManager.disconnect(this._oVirtUserAuthenticatedId);
        this._oVirtCredentialsManager = null;
    }

    selectChoice(serviceName, key) {
        this._userVerifierChoiceList.call_select_choice(serviceName, key, this._cancellable, null);
    }

    answerQuery(serviceName, answer) {
        if (!this.hasPendingMessages) {
            this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
        } else {
            let signalId = this.connect('no-more-messages', () => {
                this.disconnect(signalId);
                this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
            });
        }
    }

    _getIntervalForMessage(message) {
        // We probably could be smarter here
        return message.length * USER_READ_TIME;
    }

    finishMessageQueue() {
        if (!this.hasPendingMessages)
            return;

        this._messageQueue = [];

        this.hasPendingMessages = false;
        this.emit('no-more-messages');
    }

    _queueMessageTimeout() {
        if (this._messageQueue.length == 0) {
            this.finishMessageQueue();
            return;
        }

        if (this._messageQueueTimeoutId != 0)
            return;

        let message = this._messageQueue.shift();

        this.emit('show-message', message.text, message.type);

        this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                                                       message.interval,
                                                       () => {
                                                           this._messageQueueTimeoutId = 0;
                                                           this._queueMessageTimeout();
                                                           return GLib.SOURCE_REMOVE;
                                                       });
        GLib.Source.set_name_by_id(this._messageQueueTimeoutId, '[gnome-shell] this._queueMessageTimeout');
    }

    _queueMessage(message, messageType) {
        let interval = this._getIntervalForMessage(message);

        this.hasPendingMessages = true;
        this._messageQueue.push({ text: message, type: messageType, interval: interval });
        this._queueMessageTimeout();
    }

    _clearMessageQueue() {
        this.finishMessageQueue();

        if (this._messageQueueTimeoutId != 0) {
            GLib.source_remove(this._messageQueueTimeoutId);
            this._messageQueueTimeoutId = 0;
        }
        this.emit('show-message', null, MessageType.NONE);
    }

    _checkForFingerprintReader() {
        this._haveFingerprintReader = false;

        if (!this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY) ||
            this._fprintManager == null) {
            this._updateDefaultService();
            return;
        }

        this._fprintManager.GetDefaultDeviceRemote(Gio.DBusCallFlags.NONE, this._cancellable,
            (device, error) => {
                if (!error && device) {
                    this._haveFingerprintReader = true;
                    this._updateDefaultService();
                }
            });
    }

    _oVirtUserAuthenticated(token) {
        this._preemptingService = OVIRT_SERVICE_NAME;
        this.emit('ovirt-user-authenticated');
    }

    _checkForSmartcard() {
        let smartcardDetected;

        if (!this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY))
            smartcardDetected = false;
        else if (this._reauthOnly)
            smartcardDetected = this._smartcardManager.hasInsertedLoginToken();
        else
            smartcardDetected = this._smartcardManager.hasInsertedTokens();

        if (smartcardDetected != this.smartcardDetected) {
            this.smartcardDetected = smartcardDetected;

            if (this.smartcardDetected)
                this._preemptingService = SMARTCARD_SERVICE_NAME;
            else if (this._preemptingService == SMARTCARD_SERVICE_NAME)
                this._preemptingService = null;

            this._updateDefaultService();

            this.emit('smartcard-status-changed');
        }
    }

    _reportInitError(where, error) {
        logError(error, where);
        this._hold.release();

        this._queueMessage(_("Authentication error"), MessageType.ERROR);
        this._verificationFailed(false);
    }

    _reauthenticationChannelOpened(client, result) {
        try {
            this._clearUserVerifier();
            this._userVerifier = client.open_reauthentication_channel_finish(result);
        } catch(e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;
            if (e.matches(Gio.DBusError, Gio.DBusError.ACCESS_DENIED) &&
                !this._reauthOnly) {
                // Gdm emits org.freedesktop.DBus.Error.AccessDenied when there
                // is no session to reauthenticate. Fall back to performing
                // verification from this login session
                client.get_user_verifier(this._cancellable,
                                         this._userVerifierGot.bind(this));
                return;
            }

            this._reportInitError('Failed to open reauthentication channel', e);
            return;
        }

        if (client.get_user_verifier_choice_list)
            this._userVerifierChoiceList = client.get_user_verifier_choice_list();
        else
            this._userVerifierChoiceList = null;

        this.reauthenticating = true;
        this._connectSignals();
        this._beginVerification();
        this._hold.release();
    }

    _userVerifierGot(client, result) {
        try {
            this._clearUserVerifier();
            this._userVerifier = client.get_user_verifier_finish(result);
        } catch(e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;
            this._reportInitError('Failed to obtain user verifier', e);
            return;
        }

        if (client.get_user_verifier_choice_list)
            this._userVerifierChoiceList = client.get_user_verifier_choice_list();
        else
            this._userVerifierChoiceList = null;

        this._connectSignals();
        this._beginVerification();
        this._hold.release();
    }

    _connectSignals() {
        this._userVerifier.connect('info', this._onInfo.bind(this));
        this._userVerifier.connect('problem', this._onProblem.bind(this));
        this._userVerifier.connect('info-query', this._onInfoQuery.bind(this));
        this._userVerifier.connect('secret-info-query', this._onSecretInfoQuery.bind(this));
        this._userVerifier.connect('conversation-stopped', this._onConversationStopped.bind(this));
        this._userVerifier.connect('reset', this._onReset.bind(this));
        this._userVerifier.connect('verification-complete', this._onVerificationComplete.bind(this));

        if (this._userVerifierChoiceList)
            this._userVerifierChoiceList.connect('choice-query', this._onChoiceListQuery.bind(this));
    }

    _getForegroundService() {
        if (this._preemptingService)
            return this._preemptingService;

        return this._defaultService;
    }

    serviceIsForeground(serviceName) {
        return serviceName == this._getForegroundService();
    }

    serviceIsDefault(serviceName) {
        return serviceName == this._defaultService;
    }

    _updateDefaultService() {
        if (this._smartcardManager.loggedInWithToken())
            this._defaultService = SMARTCARD_SERVICE_NAME;
        else if (this._settings.get_boolean(PASSWORD_AUTHENTICATION_KEY))
            this._defaultService = PASSWORD_SERVICE_NAME;
        else if (this._settings.get_boolean(SMARTCARD_AUTHENTICATION_KEY))
            this._defaultService = SMARTCARD_SERVICE_NAME;
        else if (this._haveFingerprintReader)
            this._defaultService = FINGERPRINT_SERVICE_NAME;

        if (!this._defaultService) {
            log("no authentication service is enabled, using password authentication");
            this._defaultService = PASSWORD_SERVICE_NAME;
        }
    }

    _startService(serviceName) {
        this._hold.acquire();
        if (this._userName) {
           this._userVerifier.call_begin_verification_for_user(serviceName,
                                                               this._userName,
                                                               this._cancellable,
                                                               (obj, result) => {
               try {
                   obj.call_begin_verification_for_user_finish(result);
               } catch(e) {
                   if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                       return;
                   this._reportInitError('Failed to start verification for user', e);
                   return;
               }

               this._hold.release();
           });
        } else {
           this._userVerifier.call_begin_verification(serviceName,
                                                      this._cancellable,
                                                      (obj, result) => {
               try {
                   obj.call_begin_verification_finish(result);
               } catch(e) {
                   if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                       return;
                   this._reportInitError('Failed to start verification', e);
                   return;
               }

               this._hold.release();
           });
        }
    }

    _beginVerification() {
        this._startService(this._getForegroundService());

        if (this._userName && this._haveFingerprintReader && !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME))
            this._startService(FINGERPRINT_SERVICE_NAME);
    }

    _onChoiceListQuery(client, serviceName, promptMessage, list) {
        if (!this.serviceIsForeground(serviceName))
            return;

        this.emit('show-choice-list', serviceName, promptMessage, list.deep_unpack());
    }

    _onInfo(client, serviceName, info) {
        if (this.serviceIsForeground(serviceName)) {
            this._queueMessage(info, MessageType.INFO);
        } else if (serviceName == FINGERPRINT_SERVICE_NAME &&
            this._haveFingerprintReader) {
            // We don't show fingerprint messages directly since it's
            // not the main auth service. Instead we use the messages
            // as a cue to display our own message.

            // Translators: this message is shown below the password entry field
            // to indicate the user can swipe their finger instead
            this._queueMessage(_("(or swipe finger)"), MessageType.HINT);
        }
    }

    _onProblem(client, serviceName, problem) {
        if (!this.serviceIsForeground(serviceName))
            return;

        this._queueMessage(problem, MessageType.ERROR);
    }

    _onInfoQuery(client, serviceName, question) {
        if (!this.serviceIsForeground(serviceName))
            return;

        this.emit('ask-question', serviceName, question, '');
    }

    _onSecretInfoQuery(client, serviceName, secretQuestion) {
        if (!this.serviceIsForeground(serviceName))
            return;

        if (serviceName == OVIRT_SERVICE_NAME) {
            // The only question asked by this service is "Token?"
            this.answerQuery(serviceName, this._oVirtCredentialsManager.getToken());
            return;
        }

        this.emit('ask-question', serviceName, secretQuestion, '\u25cf');
    }

    _onReset() {
        // Clear previous attempts to authenticate
        this._failCounter = 0;
        this._updateDefaultService();

        this.emit('reset');
    }

    _onVerificationComplete() {
        this.emit('verification-complete');
    }

    _cancelAndReset() {
        this.cancel();
        this._onReset();
    }

    _retry() {
        this.begin(this._userName, new Batch.Hold());
    }

    _verificationFailed(retry) {
        // For Not Listed / enterprise logins, immediately reset
        // the dialog
        // Otherwise, when in login mode we allow ALLOWED_FAILURES attempts.
        // After that, we go back to the welcome screen.

        this._failCounter++;
        let canRetry = retry && this._userName &&
            (this._reauthOnly ||
             this._failCounter < this._settings.get_int(ALLOWED_FAILURES_KEY));

        if (canRetry) {
            if (!this.hasPendingMessages) {
                this._retry();
            } else {
                let signalId = this.connect('no-more-messages', () => {
                    this.disconnect(signalId);
                    if (this._cancellable && !this._cancellable.is_cancelled())
                        this._retry();
                });
            }
        } else {
            if (!this.hasPendingMessages) {
                this._cancelAndReset();
            } else {
                let signalId = this.connect('no-more-messages', () => {
                    this.disconnect(signalId);
                    this._cancelAndReset();
                });
            }
        }

        this.emit('verification-failed', canRetry);
    }

    _onConversationStopped(client, serviceName) {
        // If the login failed with the preauthenticated oVirt credentials
        // then discard the credentials and revert to default authentication
        // mechanism.
        if (this.serviceIsForeground(OVIRT_SERVICE_NAME)) {
            this._oVirtCredentialsManager.resetToken();
            this._preemptingService = null;
            this._verificationFailed(false);
            return;
        }

        // if the password service fails, then cancel everything.
        // But if, e.g., fingerprint fails, still give
        // password authentication a chance to succeed
        if (this.serviceIsForeground(serviceName)) {
            this._verificationFailed(true);
        }
    }
};
Signals.addSignalMethods(ShellUserVerifier.prototype);
(uuay)hwtest.js5!const { Clutter, Gio, Shell } = imports.gi;
const Main = imports.ui.main;
const Scripting = imports.ui.scripting;

var METRICS = {
    timeToDesktop:
    { description: "Time from starting graphical.target to desktop showing",
      units: "us" },

    overviewShowTime:
    { description: "Time to switch to overview view, first time",
      units: "us" },

    applicationsShowTime:
    { description: "Time to switch to applications view, first time",
      units: "us" },

    mainViewRedrawTime:
    { description: "Time to redraw the main view, full screen",
      units: "us" },

    overviewRedrawTime:
    { description: "Time to redraw the overview, full screen, 5 windows",
      units: "us" },

    applicationRedrawTime:
    { description: "Time to redraw frame with a maximized application update",
      units: "us" },

    geditStartTime:
    { description: "Time from gedit launch to window drawn",
      units: "us" },
}

function waitAndDraw(milliseconds) {
    let cb;

    let timeline = new Clutter.Timeline({ duration: milliseconds });
    timeline.start();

    timeline.connect('new-frame', (timeline, frame) => {
        global.stage.queue_redraw();
    });

    timeline.connect('completed', () => {
        timeline.stop();
        if (cb)
            cb();
    });

    return callback => { cb = callback; };
}

function waitSignal(object, signal) {
    let cb;

    let id = object.connect(signal, () => {
        object.disconnect(id);
        if (cb)
            cb();
    });

    return callback => { cb = callback; };
}

function extractBootTimestamp() {
    let sp = Gio.Subprocess.new(['journalctl', '-b',
                                 'MESSAGE_ID=7d4958e842da4a758f6c1cdc7b36dcc5',
                                 'UNIT=graphical.target',
                                 '-o',
                                 'json'],
                                Gio.SubprocessFlags.STDOUT_PIPE);
    let result = null;

    let datastream = Gio.DataInputStream.new(sp.get_stdout_pipe());
    while (true) {
        let [line, length] = datastream.read_line_utf8(null);
        if (line === null)
            break;

        let fields = JSON.parse(line);
        result = Number(fields['__MONOTONIC_TIMESTAMP']);
    }
    datastream.close(null);
    return result;
}

function *run() {
    Scripting.defineScriptEvent("desktopShown", "Finished initial animation");
    Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
    Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
    Scripting.defineScriptEvent("applicationsShowStart", "Starting to switch to applications view");
    Scripting.defineScriptEvent("applicationsShowDone", "Done switching to applications view");
    Scripting.defineScriptEvent("mainViewDrawStart", "Drawing main view");
    Scripting.defineScriptEvent("mainViewDrawDone", "Ending timing main view drawing");
    Scripting.defineScriptEvent("overviewDrawStart", "Drawing overview");
    Scripting.defineScriptEvent("overviewDrawDone", "Ending timing overview drawing");
    Scripting.defineScriptEvent("redrawTestStart", "Drawing application window");
    Scripting.defineScriptEvent("redrawTestDone", "Ending timing application window drawing");
    Scripting.defineScriptEvent("collectTimings", "Accumulate frame timings from redraw tests");
    Scripting.defineScriptEvent("geditLaunch", "gedit application launch");
    Scripting.defineScriptEvent("geditFirstFrame", "first frame of gedit window drawn");

    yield Scripting.waitLeisure();
    Scripting.scriptEvent('desktopShown');

    let interfaceSettings = new Gio.Settings({
        schema_id: 'org.gnome.desktop.interface'
    });
    interfaceSettings.set_boolean('enable-animations', false);

    Scripting.scriptEvent('overviewShowStart');
    Main.overview.show();
    yield Scripting.waitLeisure();
    Scripting.scriptEvent('overviewShowDone');

    yield Scripting.sleep(1000);

    Scripting.scriptEvent('applicationsShowStart');
    Main.overview._dash.showAppsButton.checked = true;

    yield Scripting.waitLeisure();
    Scripting.scriptEvent('applicationsShowDone');

    yield Scripting.sleep(1000);

    Main.overview.hide();
    yield Scripting.waitLeisure();

    ////////////////////////////////////////
    // Tests of redraw speed
    ////////////////////////////////////////

    global.frame_timestamps = true;
    global.frame_finish_timestamp = true;

    for (let k = 0; k < 5; k++)
        yield Scripting.createTestWindow({ maximized: true });
    yield Scripting.waitTestWindows();

    yield Scripting.sleep(1000);

    Scripting.scriptEvent('mainViewDrawStart');
    yield waitAndDraw(1000);
    Scripting.scriptEvent('mainViewDrawDone');

    Main.overview.show();
    Scripting.waitLeisure();

    yield Scripting.sleep(1500);

    Scripting.scriptEvent('overviewDrawStart');
    yield waitAndDraw(1000);
    Scripting.scriptEvent('overviewDrawDone');

    yield Scripting.destroyTestWindows();
    Main.overview.hide();

    yield Scripting.createTestWindow({ maximized: true,
                                       redraws: true});
    yield Scripting.waitTestWindows();

    yield Scripting.sleep(1000);

    Scripting.scriptEvent('redrawTestStart');
    yield Scripting.sleep(1000);
    Scripting.scriptEvent('redrawTestDone');

    yield Scripting.sleep(1000);
    Scripting.scriptEvent('collectTimings');

    yield Scripting.destroyTestWindows();

    global.frame_timestamps = false;
    global.frame_finish_timestamp = false;

    yield Scripting.sleep(1000);

    ////////////////////////////////////////

    let appSys = Shell.AppSystem.get_default();
    let app = appSys.lookup_app('org.gnome.gedit.desktop');

    Scripting.scriptEvent('geditLaunch');
    app.activate();

    let windows = app.get_windows();
    if (windows.length > 0)
        throw new Error('gedit was already running');

    while (windows.length == 0) {
        yield waitSignal(global.display, 'window-created');
        windows = app.get_windows();
    }

    let actor = windows[0].get_compositor_private();
    yield waitSignal(actor, 'first-frame');
    Scripting.scriptEvent('geditFirstFrame');

    yield Scripting.sleep(1000);

    windows[0].delete(global.get_current_time());

    yield Scripting.sleep(1000);

    interfaceSettings.set_boolean('enable-animations', true);
}

let overviewShowStart;
let applicationsShowStart;
let stagePaintStart;
let redrawTiming;
let redrawTimes = {};
let geditLaunchTime;

function script_desktopShown(time) {
    let bootTimestamp = extractBootTimestamp();
    METRICS.timeToDesktop.value = time - bootTimestamp;
}

function script_overviewShowStart(time) {
    overviewShowStart = time;
}

function script_overviewShowDone(time) {
    METRICS.overviewShowTime.value = time - overviewShowStart;
}

function script_applicationsShowStart(time) {
    applicationsShowStart = time;
}

function script_applicationsShowDone(time) {
    METRICS.applicationsShowTime.value = time - applicationsShowStart;
}

function script_mainViewDrawStart(time) {
    redrawTiming = 'mainView';
}

function script_mainViewDrawDone(time) {
    redrawTiming = null;
}

function script_overviewDrawStart(time) {
    redrawTiming = 'overview';
}

function script_overviewDrawDone(time) {
    redrawTiming = null;
}

function script_redrawTestStart(time) {
    redrawTiming = 'application';
}

function script_redrawTestDone(time) {
    redrawTiming = null;
}

function script_collectTimings(time) {
    for (let timing in redrawTimes) {
        let times = redrawTimes[timing];
        times.sort((a, b) => a - b);

        let len = times.length;
        let median;

        if (len == 0)
            median = -1;
        else if (len % 2 == 1)
            median = times[(len - 1)/ 2];
        else
            median = Math.round((times[len / 2 - 1] + times[len / 2]) / 2);

        METRICS[timing + 'RedrawTime'].value = median;
    }
}

function script_geditLaunch(time) {
    geditLaunchTime = time;
}

function script_geditFirstFrame(time) {
    METRICS.geditStartTime.value = time - geditLaunchTime;
}

function clutter_stagePaintStart(time) {
    stagePaintStart = time;
}

function clutter_paintCompletedTimestamp(time) {
    if (redrawTiming != null && stagePaintStart != null) {
        if (!(redrawTiming in redrawTimes))
            redrawTimes[redrawTiming] = [];
        redrawTimes[redrawTiming].push(time - stagePaintStart);
    }
    stagePaintStart = null;
}
(uuay)lookingGlass.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Cogl, Gio, GLib,
        GObject, Meta, Pango, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;
const System = imports.system;

const History = imports.misc.history;
const ExtensionUtils = imports.misc.extensionUtils;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const Main = imports.ui.main;
const JsParse = imports.misc.jsParse;

const { ExtensionState } = ExtensionUtils;

const CHEVRON = '>>> ';

/* Imports...feel free to add here as needed */
var commandHeader = 'const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi; ' +
                    'const Main = imports.ui.main; ' +
                    'const Mainloop = imports.mainloop; ' +
                    'const Tweener = imports.ui.tweener; ' +
                    /* Utility functions...we should probably be able to use these
                     * in the shell core code too. */
                    'const stage = global.stage; ' +
                    /* Special lookingGlass functions */
                    'const inspect = Main.lookingGlass.inspect.bind(Main.lookingGlass); ' +
                    'const it = Main.lookingGlass.getIt(); ' +
                    'const r = Main.lookingGlass.getResult.bind(Main.lookingGlass); ';

const HISTORY_KEY = 'looking-glass-history';
// Time between tabs for them to count as a double-tab event
var AUTO_COMPLETE_DOUBLE_TAB_DELAY = 500;
var AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION = 0.2;
var AUTO_COMPLETE_GLOBAL_KEYWORDS = _getAutoCompleteGlobalKeywords();

function _getAutoCompleteGlobalKeywords() {
    const keywords = ['true', 'false', 'null', 'new'];
    // Don't add the private properties of window (i.e., ones starting with '_')
    const windowProperties = Object.getOwnPropertyNames(window).filter(
        a => a.charAt(0) != '_'
    );
    const headerProperties = JsParse.getDeclaredConstants(commandHeader);

    return keywords.concat(windowProperties).concat(headerProperties);
}

var AutoComplete = class AutoComplete {
    constructor(entry) {
        this._entry = entry;
        this._entry.connect('key-press-event', this._entryKeyPressEvent.bind(this));
        this._lastTabTime = global.get_current_time();
    }

    _processCompletionRequest(event) {
        if (event.completions.length == 0) {
            return;
        }
        // Unique match = go ahead and complete; multiple matches + single tab = complete the common starting string;
        // multiple matches + double tab = emit a suggest event with all possible options
        if (event.completions.length == 1) {
            this.additionalCompletionText(event.completions[0], event.attrHead);
            this.emit('completion', { completion: event.completions[0], type: 'whole-word' });
        } else if (event.completions.length > 1 && event.tabType === 'single') {
            let commonPrefix = JsParse.getCommonPrefix(event.completions);

            if (commonPrefix.length > 0) {
                this.additionalCompletionText(commonPrefix, event.attrHead);
                this.emit('completion', { completion: commonPrefix, type: 'prefix' });
                this.emit('suggest', { completions: event.completions});
            }
        } else if (event.completions.length > 1 && event.tabType === 'double') {
            this.emit('suggest', { completions: event.completions});
        }
    }

    _entryKeyPressEvent(actor, event) {
        let cursorPos = this._entry.clutter_text.get_cursor_position();
        let text = this._entry.get_text();
        if (cursorPos != -1) {
            text = text.slice(0, cursorPos);
        }
        if (event.get_key_symbol() == Clutter.Tab) {
            let [completions, attrHead] = JsParse.getCompletions(text, commandHeader, AUTO_COMPLETE_GLOBAL_KEYWORDS);
            let currTime = global.get_current_time();
            if ((currTime - this._lastTabTime) < AUTO_COMPLETE_DOUBLE_TAB_DELAY) {
                this._processCompletionRequest({ tabType: 'double',
                                                 completions: completions,
                                                 attrHead: attrHead });
            } else {
                this._processCompletionRequest({ tabType: 'single',
                                                 completions: completions,
                                                 attrHead: attrHead });
            }
            this._lastTabTime = currTime;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    // Insert characters of text not already included in head at cursor position.  i.e., if text="abc" and head="a",
    // the string "bc" will be appended to this._entry
    additionalCompletionText(text, head) {
        let additionalCompletionText = text.slice(head.length);
        let cursorPos = this._entry.clutter_text.get_cursor_position();

        this._entry.clutter_text.insert_text(additionalCompletionText, cursorPos);
    }
};
Signals.addSignalMethods(AutoComplete.prototype);


var Notebook = class Notebook {
    constructor() {
        this.actor = new St.BoxLayout({ vertical: true });

        this.tabControls = new St.BoxLayout({ style_class: 'labels' });

        this._selectedIndex = -1;
        this._tabs = [];
    }

    appendPage(name, child) {
        let labelBox = new St.BoxLayout({ style_class: 'notebook-tab',
                                          reactive: true,
                                          track_hover: true });
        let label = new St.Button({ label: name });
        label.connect('clicked', () => {
            this.selectChild(child);
            return true;
        });
        labelBox.add(label, { expand: true });
        this.tabControls.add(labelBox);

        let scrollview = new St.ScrollView({ x_fill: true, y_fill: true });
        scrollview.get_hscroll_bar().hide();
        scrollview.add_actor(child);

        let tabData = { child: child,
                        labelBox: labelBox,
                        label: label,
                        scrollView: scrollview,
                        _scrollToBottom: false };
        this._tabs.push(tabData);
        scrollview.hide();
        this.actor.add(scrollview, { expand: true });

        let vAdjust = scrollview.vscroll.adjustment;
        vAdjust.connect('changed', () => { this._onAdjustScopeChanged(tabData); });
        vAdjust.connect('notify::value', () => { this._onAdjustValueChanged(tabData); });

        if (this._selectedIndex == -1)
            this.selectIndex(0);
    }

    _unselect() {
        if (this._selectedIndex < 0)
            return;
        let tabData = this._tabs[this._selectedIndex];
        tabData.labelBox.remove_style_pseudo_class('selected');
        tabData.scrollView.hide();
        this._selectedIndex = -1;
    }

    selectIndex(index) {
        if (index == this._selectedIndex)
            return;
        if (index < 0) {
            this._unselect();
            this.emit('selection', null);
            return;
        }

        // Focus the new tab before unmapping the old one
        let tabData = this._tabs[index];
        if (!tabData.scrollView.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
            this.actor.grab_key_focus();

        this._unselect();

        tabData.labelBox.add_style_pseudo_class('selected');
        tabData.scrollView.show();
        this._selectedIndex = index;
        this.emit('selection', tabData.child);
    }

    selectChild(child) {
        if (child == null)
            this.selectIndex(-1);
        else {
            for (let i = 0; i < this._tabs.length; i++) {
                let tabData = this._tabs[i];
                if (tabData.child == child) {
                    this.selectIndex(i);
                    return;
                }
            }
        }
    }

    scrollToBottom(index) {
        let tabData = this._tabs[index];
        tabData._scrollToBottom = true;

    }

    _onAdjustValueChanged(tabData) {
        let vAdjust = tabData.scrollView.vscroll.adjustment;
        if (vAdjust.value < (vAdjust.upper - vAdjust.lower - 0.5))
            tabData._scrolltoBottom = false;
    }

    _onAdjustScopeChanged(tabData) {
        if (!tabData._scrollToBottom)
            return;
        let vAdjust = tabData.scrollView.vscroll.adjustment;
        vAdjust.value = vAdjust.upper - vAdjust.page_size;
    }

    nextTab() {
        let nextIndex = this._selectedIndex;
        if (nextIndex < this._tabs.length - 1) {
            ++nextIndex;
        }

        this.selectIndex(nextIndex);
    }

    prevTab() {
        let prevIndex = this._selectedIndex;
        if (prevIndex > 0) {
            --prevIndex;
        }

        this.selectIndex(prevIndex);
    }
};
Signals.addSignalMethods(Notebook.prototype);

function objectToString(o) {
    if (typeof(o) == typeof(objectToString)) {
        // special case this since the default is way, way too verbose
        return '<js function>';
    } else {
        return '' + o;
    }
}

var ObjLink = class ObjLink {
    constructor(lookingGlass, o, title) {
        let text;
        if (title)
            text = title;
        else
            text = objectToString(o);
        text = GLib.markup_escape_text(text, -1);
        this._obj = o;

        this.actor = new St.Button({ reactive: true,
                                     track_hover: true,
                                     style_class: 'shell-link',
                                     label: text });
        this.actor.get_child().single_line_mode = true;
        this.actor.connect('clicked', this._onClicked.bind(this));

        this._lookingGlass = lookingGlass;
    }

    _onClicked(link) {
        this._lookingGlass.inspectObject(this._obj, this.actor);
    }
};

var Result = class Result {
    constructor(lookingGlass, command, o, index) {
        this.index = index;
        this.o = o;

        this.actor = new St.BoxLayout({ vertical: true });
        this._lookingGlass = lookingGlass;

        let cmdTxt = new St.Label({ text: command });
        cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        this.actor.add(cmdTxt);
        let box = new St.BoxLayout({});
        this.actor.add(box);
        let resultTxt = new St.Label({ text: 'r(' + index + ') = ' });
        resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        box.add(resultTxt);
        let objLink = new ObjLink(this._lookingGlass, o);
        box.add(objLink.actor);
    }
};

var WindowList = class WindowList {
    constructor(lookingGlass) {
        this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
        let tracker = Shell.WindowTracker.get_default();
        this._updateId = Main.initializeDeferredWork(this.actor, this._updateWindowList.bind(this));
        global.display.connect('window-created', this._updateWindowList.bind(this));
        tracker.connect('tracked-windows-changed', this._updateWindowList.bind(this));

        this._lookingGlass = lookingGlass;
    }

    _updateWindowList() {
        this.actor.destroy_all_children();
        let windows = global.get_window_actors();
        let tracker = Shell.WindowTracker.get_default();
        for (let i = 0; i < windows.length; i++) {
            let metaWindow = windows[i].metaWindow;
            // Avoid multiple connections
            if (!metaWindow._lookingGlassManaged) {
                metaWindow.connect('unmanaged', this._updateWindowList.bind(this));
                metaWindow._lookingGlassManaged = true;
            }
            let box = new St.BoxLayout({ vertical: true });
            this.actor.add(box);
            let windowLink = new ObjLink(this._lookingGlass, metaWindow, metaWindow.title);
            box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false });
            let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' });
            box.add(propsBox);
            propsBox.add(new St.Label({ text: 'wmclass: ' + metaWindow.get_wm_class() }));
            let app = tracker.get_window_app(metaWindow);
            if (app != null && !app.is_window_backed()) {
                let icon = app.create_icon_texture(22);
                let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' });
                propsBox.add(propBox);
                propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false });
                let appLink = new ObjLink(this._lookingGlass, app, app.get_id());
                propBox.add(appLink.actor, { y_fill: false });
                propBox.add(icon, { y_fill: false });
            } else {
                propsBox.add(new St.Label({ text: '<untracked>' }));
            }
        }
    }
};
Signals.addSignalMethods(WindowList.prototype);

var ObjInspector = class ObjInspector {
    constructor(lookingGlass) {
        this._obj = null;
        this._previousObj = null;

        this._parentList = [];

        this.actor = new St.ScrollView({ pivot_point: new Clutter.Point({ x: 0.5, y: 0.5 }),
                                         x_fill: true, y_fill: true });
        this.actor.get_hscroll_bar().hide();
        this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector',
                                             style_class: 'lg-dialog',
                                             vertical: true });
        this.actor.add_actor(this._container);

        this._lookingGlass = lookingGlass;
    }

    selectObject(obj, skipPrevious) {
        if (!skipPrevious)
            this._previousObj = this._obj;
        else
            this._previousObj = null;
        this._obj = obj;

        this._container.destroy_all_children();

        let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' });
        this._container.add_actor(hbox);
        let label = new St.Label({ text: 'Inspecting: %s: %s'.format(typeof(obj),
                                                                     objectToString(obj)) });
        label.single_line_mode = true;
        hbox.add(label, { expand: true, y_fill: false });
        let button = new St.Button({ label: 'Insert', style_class: 'lg-obj-inspector-button' });
        button.connect('clicked', this._onInsert.bind(this));
        hbox.add(button);

        if (this._previousObj != null) {
            button = new St.Button({ label: 'Back', style_class: 'lg-obj-inspector-button' });
            button.connect('clicked', this._onBack.bind(this));
            hbox.add(button);
        }

        button = new St.Button({ style_class: 'window-close' });
        button.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' }));
        button.connect('clicked', this.close.bind(this));
        hbox.add(button);
        if (typeof(obj) == typeof({})) {
            let properties = [];
            for (let propName in obj) {
                properties.push(propName);
            }
            properties.sort();

            for (let i = 0; i < properties.length; i++) {
                let propName = properties[i];
                let valueStr;
                let link;
                try {
                    let prop = obj[propName];
                    link = new ObjLink(this._lookingGlass, prop).actor;
                } catch (e) {
                    link = new St.Label({ text: '<error>' });
                }
                let hbox = new St.BoxLayout();
                let propText = propName + ': ' + valueStr;
                hbox.add(new St.Label({ text: propName + ': ' }));
                hbox.add(link);
                this._container.add_actor(hbox);
            }
        }
    }

    open(sourceActor) {
        if (this._open)
            return;
        this._previousObj = null;
        this._open = true;
        this.actor.show();
        if (sourceActor) {
            this.actor.set_scale(0, 0);
            Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1,
                                           transition: 'easeOutQuad',
                                           time: 0.2 });
        } else {
            this.actor.set_scale(1, 1);
        }
    }

    close() {
        if (!this._open)
            return;
        this._open = false;
        this.actor.hide();
        this._previousObj = null;
        this._obj = null;
    }

    _onInsert() {
        let obj = this._obj;
        this.close();
        this._lookingGlass.insertObject(obj);
    }

    _onBack() {
        this.selectObject(this._previousObj, true);
    }
};

var RedBorderEffect = GObject.registerClass(
class RedBorderEffect extends Clutter.Effect {
    vfunc_paint() {
        let actor = this.get_actor();
        actor.continue_paint();

        let color = new Cogl.Color();
        color.init_from_4ub(0xff, 0, 0, 0xc4);
        Cogl.set_source_color(color);

        let geom = actor.get_allocation_geometry();
        let width = 2;

        // clockwise order
        Cogl.rectangle(0, 0, geom.width, width);
        Cogl.rectangle(geom.width - width, width,
                       geom.width, geom.height);
        Cogl.rectangle(0, geom.height,
                       geom.width - width, geom.height - width);
        Cogl.rectangle(0, geom.height - width,
                       width, width);
    }
});

var Inspector = GObject.registerClass({
    Signals: { 'closed': {},
               'target': { param_types: [Clutter.Actor.$gtype, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] } },
}, class Inspector extends Clutter.Actor {
    _init(lookingGlass) {
        super._init({ width: 0,
                      height: 0 });

        Main.uiGroup.add_actor(this);

        let eventHandler = new St.BoxLayout({ name: 'LookingGlassDialog',
                                              vertical: false,
                                              reactive: true });
        this._eventHandler = eventHandler;
        this.add_actor(eventHandler);
        this._displayText = new St.Label();
        eventHandler.add(this._displayText, { expand: true });

        eventHandler.connect('key-press-event', this._onKeyPressEvent.bind(this));
        eventHandler.connect('button-press-event', this._onButtonPressEvent.bind(this));
        eventHandler.connect('scroll-event', this._onScrollEvent.bind(this));
        eventHandler.connect('motion-event', this._onMotionEvent.bind(this));
        Clutter.grab_pointer(eventHandler);
        Clutter.grab_keyboard(eventHandler);

        // this._target is the actor currently shown by the inspector.
        // this._pointerTarget is the actor directly under the pointer.
        // Normally these are the same, but if you use the scroll wheel
        // to drill down, they'll diverge until you either scroll back
        // out, or move the pointer outside of _pointerTarget.
        this._target = null;
        this._pointerTarget = null;

        this._lookingGlass = lookingGlass;
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        if (!this._eventHandler)
            return;

        let primary = Main.layoutManager.primaryMonitor;

        let [minWidth, minHeight, natWidth, natHeight] =
            this._eventHandler.get_preferred_size();

        let childBox = new Clutter.ActorBox();
        childBox.x1 = primary.x + Math.floor((primary.width - natWidth) / 2);
        childBox.x2 = childBox.x1 + natWidth;
        childBox.y1 = primary.y + Math.floor((primary.height - natHeight) / 2);
        childBox.y2 = childBox.y1 + natHeight;
        this._eventHandler.allocate(childBox, flags);
    }

    _close() {
        Clutter.ungrab_pointer();
        Clutter.ungrab_keyboard();
        this._eventHandler.destroy();
        this._eventHandler = null;
        this.emit('closed');
    }

    _onKeyPressEvent(actor, event) {
        if (event.get_key_symbol() == Clutter.Escape)
            this._close();
        return Clutter.EVENT_STOP;
    }

    _onButtonPressEvent(actor, event) {
        if (this._target) {
            let [stageX, stageY] = event.get_coords();
            this.emit('target', this._target, stageX, stageY);
        }
        this._close();
        return Clutter.EVENT_STOP;
    }

    _onScrollEvent(actor, event) {
        switch (event.get_scroll_direction()) {
        case Clutter.ScrollDirection.UP:
            // select parent
            let parent = this._target.get_parent();
            if (parent != null) {
                this._target = parent;
                this._update(event);
            }
            break;

        case Clutter.ScrollDirection.DOWN:
            // select child
            if (this._target != this._pointerTarget) {
                let child = this._pointerTarget;
                while (child) {
                    let parent = child.get_parent();
                    if (parent == this._target)
                        break;
                    child = parent;
                }
                if (child) {
                    this._target = child;
                    this._update(event);
                }
            }
            break;

        default:
            break;
        }
        return Clutter.EVENT_STOP;
    }

    _onMotionEvent(actor, event) {
        this._update(event);
        return Clutter.EVENT_STOP;
    }

    _update(event) {
        let [stageX, stageY] = event.get_coords();
        let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL,
                                                   stageX,
                                                   stageY);

        if (target != this._pointerTarget)
            this._target = target;
        this._pointerTarget = target;

        let position = '[inspect x: ' + stageX + ' y: ' + stageY + ']';
        this._displayText.text = '';
        this._displayText.text = position + ' ' + this._target;

        this._lookingGlass.setBorderPaintTarget(this._target);
    }
});

var Extensions = class Extensions {
    constructor(lookingGlass) {
        this._lookingGlass = lookingGlass;
        this.actor = new St.BoxLayout({ vertical: true,
                                        name: 'lookingGlassExtensions' });
        this._noExtensions = new St.Label({ style_class: 'lg-extensions-none',
                                             text: _("No extensions installed") });
        this._numExtensions = 0;
        this._extensionsList = new St.BoxLayout({ vertical: true,
                                                  style_class: 'lg-extensions-list' });
        this._extensionsList.add(this._noExtensions);
        this.actor.add(this._extensionsList);

        Main.extensionManager.getUuids().forEach(uuid => {
            this._loadExtension(null, uuid);
        });

        Main.extensionManager.connect('extension-loaded',
                                      this._loadExtension.bind(this));
    }

    _loadExtension(o, uuid) {
        let extension = Main.extensionManager.lookup(uuid);
        // There can be cases where we create dummy extension metadata
        // that's not really a proper extension. Don't bother with these.
        if (!extension.metadata.name)
            return;

        let extensionDisplay = this._createExtensionDisplay(extension);
        if (this._numExtensions == 0)
            this._extensionsList.remove_actor(this._noExtensions);

        this._numExtensions ++;
        this._extensionsList.add(extensionDisplay);
    }

    _onViewSource(actor) {
        let extension = actor._extension;
        let uri = extension.dir.get_uri();
        Gio.app_info_launch_default_for_uri(uri, global.create_app_launch_context(0, -1));
        this._lookingGlass.close();
    }

    _onWebPage(actor) {
        let extension = actor._extension;
        Gio.app_info_launch_default_for_uri(extension.metadata.url, global.create_app_launch_context(0, -1));
        this._lookingGlass.close();
    }

    _onViewErrors(actor) {
        let extension = actor._extension;
        let shouldShow = !actor._isShowing;

        if (shouldShow) {
            let errors = extension.errors;
            let errorDisplay = new St.BoxLayout({ vertical: true });
            if (errors && errors.length) {
                for (let i = 0; i < errors.length; i ++)
                    errorDisplay.add(new St.Label({ text: errors[i] }));
            } else {
                /* Translators: argument is an extension UUID. */
                let message = _("%s has not emitted any errors.").format(extension.uuid);
                errorDisplay.add(new St.Label({ text: message }));
            }

            actor._errorDisplay = errorDisplay;
            actor._parentBox.add(errorDisplay);
            actor.label = _("Hide Errors");
        } else {
            actor._errorDisplay.destroy();
            actor._errorDisplay = null;
            actor.label = _("Show Errors");
        }

        actor._isShowing = shouldShow;
    }

    _stateToString(extensionState) {
        switch (extensionState) {
            case ExtensionState.ENABLED:
                return _("Enabled");
            case ExtensionState.DISABLED:
            case ExtensionState.INITIALIZED:
                return _("Disabled");
            case ExtensionState.ERROR:
                return _("Error");
            case ExtensionState.OUT_OF_DATE:
                return _("Out of date");
            case ExtensionState.DOWNLOADING:
                return _("Downloading");
        }
        return 'Unknown'; // Not translated, shouldn't appear
    }

    _createExtensionDisplay(extension) {
        let box = new St.BoxLayout({ style_class: 'lg-extension', vertical: true });
        let name = new St.Label({ style_class: 'lg-extension-name',
                                   text: extension.metadata.name });
        box.add(name, { expand: true });
        let description = new St.Label({ style_class: 'lg-extension-description',
                                         text: extension.metadata.description || 'No description' });
        box.add(description, { expand: true });

        let metaBox = new St.BoxLayout({ style_class: 'lg-extension-meta' });
        box.add(metaBox);
        let stateString = this._stateToString(extension.state);
        let state = new St.Label({ style_class: 'lg-extension-state',
                                   text: this._stateToString(extension.state) });
        metaBox.add(state);

        let viewsource = new St.Button({ reactive: true,
                                         track_hover: true,
                                         style_class: 'shell-link',
                                         label: _("View Source") });
        viewsource._extension = extension;
        viewsource.connect('clicked', this._onViewSource.bind(this));
        metaBox.add(viewsource);

        if (extension.metadata.url) {
            let webpage = new St.Button({ reactive: true,
                                          track_hover: true,
                                          style_class: 'shell-link',
                                          label: _("Web Page") });
            webpage._extension = extension;
            webpage.connect('clicked', this._onWebPage.bind(this));
            metaBox.add(webpage);
        }

        let viewerrors = new St.Button({ reactive: true,
                                         track_hover: true,
                                         style_class: 'shell-link',
                                         label: _("Show Errors") });
        viewerrors._extension = extension;
        viewerrors._parentBox = box;
        viewerrors._isShowing = false;
        viewerrors.connect('clicked', this._onViewErrors.bind(this));
        metaBox.add(viewerrors);

        return box;
    }
};

var LookingGlass = class LookingGlass {
    constructor() {
        this._borderPaintTarget = null;
        this._redBorderEffect = new RedBorderEffect();

        this._open = false;

        this._it = null;
        this._offset = 0;
        this._results = [];

        // Sort of magic, but...eh.
        this._maxItems = 150;

        this.actor = new St.BoxLayout({ name: 'LookingGlassDialog',
                                        style_class: 'lg-dialog',
                                        vertical: true,
                                        visible: false,
                                        reactive: true });
        this.actor.connect('key-press-event', this._globalKeyPressEvent.bind(this));

        this._interfaceSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.interface' });
        this._interfaceSettings.connect('changed::monospace-font-name',
                                        this._updateFont.bind(this));
        this._updateFont();

        // We want it to appear to slide out from underneath the panel
        Main.uiGroup.add_actor(this.actor);
        Main.uiGroup.set_child_below_sibling(this.actor,
                                             Main.layoutManager.panelBox);
        Main.layoutManager.panelBox.connect('allocation-changed',
                                            this._queueResize.bind(this));
        Main.layoutManager.keyboardBox.connect('allocation-changed',
                                               this._queueResize.bind(this));

        this._objInspector = new ObjInspector(this);
        Main.uiGroup.add_actor(this._objInspector.actor);
        this._objInspector.actor.hide();

        let toolbar = new St.BoxLayout({ name: 'Toolbar' });
        this.actor.add_actor(toolbar);
        let inspectIcon = new St.Icon({ icon_name: 'gtk-color-picker',
                                        icon_size: 24 });
        toolbar.add_actor(inspectIcon);
        inspectIcon.reactive = true;
        inspectIcon.connect('button-press-event', () => {
            let inspector = new Inspector(this);
            inspector.connect('target', (i, target, stageX, stageY) => {
                this._pushResult('inspect(' + Math.round(stageX) + ', ' + Math.round(stageY) + ')', target);
            });
            inspector.connect('closed', () => {
                this.actor.show();
                global.stage.set_key_focus(this._entry);
            });
            this.actor.hide();
            return Clutter.EVENT_STOP;
        });

        let gcIcon = new St.Icon({ icon_name: 'user-trash-full',
                                   icon_size: 24 });
        toolbar.add_actor(gcIcon);
        gcIcon.reactive = true;
        gcIcon.connect('button-press-event', () => {
           gcIcon.icon_name = 'user-trash';
           System.gc();
           this._timeoutId = Mainloop.timeout_add(500, () => {
                gcIcon.icon_name = 'user-trash-full';
                this._timeoutId = 0;
                return GLib.SOURCE_REMOVE;
           });
           GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] gcIcon.icon_name = \'user-trash-full\'');
           return Clutter.EVENT_PROPAGATE;
        });

        let notebook = new Notebook();
        this._notebook = notebook;
        this.actor.add(notebook.actor, { expand: true });

        let emptyBox = new St.Bin();
        toolbar.add(emptyBox, { expand: true });
        toolbar.add_actor(notebook.tabControls);

        this._evalBox = new St.BoxLayout({ name: 'EvalBox', vertical: true });
        notebook.appendPage('Evaluator', this._evalBox);

        this._resultsArea = new St.BoxLayout({ name: 'ResultsArea', vertical: true });
        this._evalBox.add(this._resultsArea, { expand: true });

        this._entryArea = new St.BoxLayout({ name: 'EntryArea' });
        this._evalBox.add_actor(this._entryArea);

        let label = new St.Label({ text: CHEVRON });
        this._entryArea.add(label);

        this._entry = new St.Entry({ can_focus: true });
        ShellEntry.addContextMenu(this._entry);
        this._entryArea.add(this._entry, { expand: true });

        this._windowList = new WindowList(this);
        notebook.appendPage('Windows', this._windowList.actor);

        this._extensions = new Extensions(this);
        notebook.appendPage('Extensions', this._extensions.actor);

        this._entry.clutter_text.connect('activate', (o, e) => {
            // Hide any completions we are currently showing
            this._hideCompletions();

            let text = o.get_text();
            // Ensure we don't get newlines in the command; the history file is
            // newline-separated.
            text = text.replace('\n', ' ');
            // Strip leading and trailing whitespace
            text = text.replace(/^\s+/g, '').replace(/\s+$/g, '');
            if (text == '')
                return true;
            this._evaluate(text);
            return true;
        });

        this._history = new History.HistoryManager({ gsettingsKey: HISTORY_KEY, 
                                                     entry: this._entry.clutter_text });

        this._autoComplete = new AutoComplete(this._entry);
        this._autoComplete.connect('suggest', (a, e) => {
            this._showCompletions(e.completions);
        });
        // If a completion is completed unambiguously, the currently-displayed completion
        // suggestions become irrelevant.
        this._autoComplete.connect('completion', (a, e) => {
            if (e.type == 'whole-word')
                this._hideCompletions();
        });

        this._resize();
    }

    _updateFont() {
        let fontName = this._interfaceSettings.get_string('monospace-font-name');
        let fontDesc = Pango.FontDescription.from_string(fontName);
        // We ignore everything but size and style; you'd be crazy to set your system-wide
        // monospace font to be bold/oblique/etc. Could easily be added here.
        this.actor.style =
            'font-size: ' + fontDesc.get_size() / 1024. + (fontDesc.get_size_is_absolute() ? 'px' : 'pt') + ';'
            + 'font-family: "' + fontDesc.get_family() + '";';
    }

    setBorderPaintTarget(obj) {
        if (this._borderPaintTarget != null)
            this._borderPaintTarget.remove_effect(this._redBorderEffect);
        this._borderPaintTarget = obj;
        if (this._borderPaintTarget != null)
            this._borderPaintTarget.add_effect(this._redBorderEffect);
    }

    _pushResult(command, obj) {
        let index = this._results.length + this._offset;
        let result = new Result(this, CHEVRON + command, obj, index);
        this._results.push(result);
        this._resultsArea.add(result.actor);
        if (obj instanceof Clutter.Actor)
            this.setBorderPaintTarget(obj);

        let children = this._resultsArea.get_children();
        if (children.length > this._maxItems) {
            this._results.shift();
            children[0].destroy();
            this._offset++;
        }
        this._it = obj;

        // Scroll to bottom
        this._notebook.scrollToBottom(0);
    }

    _showCompletions(completions) {
        if (!this._completionActor) {
            this._completionActor = new St.Label({ name: 'LookingGlassAutoCompletionText', style_class: 'lg-completions-text' });
            this._completionActor.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
            this._completionActor.clutter_text.line_wrap = true;
            this._evalBox.insert_child_below(this._completionActor, this._entryArea);
        }

        this._completionActor.set_text(completions.join(', '));

        // Setting the height to -1 allows us to get its actual preferred height rather than
        // whatever was last given in set_height by Tweener.
        this._completionActor.set_height(-1);
        let [minHeight, naturalHeight] = this._completionActor.get_preferred_height(this._resultsArea.get_width());

        // Don't reanimate if we are already visible
        if (this._completionActor.visible) {
            this._completionActor.height = naturalHeight;
        } else {
            this._completionActor.show();
            Tweener.removeTweens(this._completionActor);
            Tweener.addTween(this._completionActor, { time: AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / St.get_slow_down_factor(),
                                                      transition: 'easeOutQuad',
                                                      height: naturalHeight,
                                                      opacity: 255
                                                    });
        }
    }

    _hideCompletions() {
        if (this._completionActor) {
            Tweener.removeTweens(this._completionActor);
            Tweener.addTween(this._completionActor, { time: AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / St.get_slow_down_factor(),
                                                      transition: 'easeOutQuad',
                                                      height: 0,
                                                      opacity: 0,
                                                      onComplete: () => {
                                                          this._completionActor.hide();
                                                      }
                                                    });
        }
    }

    _evaluate(command) {
        this._history.addItem(command);

        let lines = command.split(';');
        lines.push(`return ${lines.pop()}`);

        let fullCmd = commandHeader + lines.join(';');

        let resultObj;
        try {
            resultObj = Function(fullCmd)();
        } catch (e) {
            resultObj = '<exception ' + e + '>';
        }

        this._pushResult(command, resultObj);
        this._entry.text = '';
    }

    inspect(x, y) {
        return global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
    }

    getIt() {
        return this._it;
    }

    getResult(idx) {
        return this._results[idx - this._offset].o;
    }

    toggle() {
        if (this._open)
            this.close();
        else
            this.open();
    }

    _queueResize() {
        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => { this._resize(); });
    }

    _resize() {
        let primary = Main.layoutManager.primaryMonitor;
        let myWidth = primary.width * 0.7;
        let availableHeight = primary.height - Main.layoutManager.keyboardBox.height;
        let myHeight = Math.min(primary.height * 0.7, availableHeight * 0.9);
        this.actor.x = primary.x + (primary.width - myWidth) / 2;
        this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight;
        this._targetY = this._hiddenY + myHeight;
        this.actor.y = this._hiddenY;
        this.actor.width = myWidth;
        this.actor.height = myHeight;
        this._objInspector.actor.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
        this._objInspector.actor.set_position(this.actor.x + Math.floor(myWidth * 0.1),
                                              this._targetY + Math.floor(myHeight * 0.1));
    }

    insertObject(obj) {
        this._pushResult('<insert>', obj);
    }

    inspectObject(obj, sourceActor) {
        this._objInspector.open(sourceActor);
        this._objInspector.selectObject(obj);
    }

    // Handle key events which are relevant for all tabs of the LookingGlass
    _globalKeyPressEvent(actor, event) {
        let symbol = event.get_key_symbol();
        let modifierState = event.get_state();
        if (symbol == Clutter.Escape) {
            if (this._objInspector.actor.visible) {
                this._objInspector.close();
            } else {
                this.close();
            }
            return Clutter.EVENT_STOP;
        }
        // Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view
        if (modifierState & Clutter.ModifierType.CONTROL_MASK) {
            if (symbol == Clutter.KEY_Page_Up) {
                this._notebook.prevTab();
            } else if (symbol == Clutter.KEY_Page_Down) {
                this._notebook.nextTab();
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    open() {
        if (this._open)
            return;

        if (!Main.pushModal(this._entry, { actionMode: Shell.ActionMode.LOOKING_GLASS }))
            return;

        this._notebook.selectIndex(0);
        this.actor.show();
        this._open = true;
        this._history.lastItem();

        Tweener.removeTweens(this.actor);

        // We inverse compensate for the slow-down so you can change the factor
        // through LookingGlass without long waits.
        Tweener.addTween(this.actor, { time: 0.5 / St.get_slow_down_factor(),
                                       transition: 'easeOutQuad',
                                       y: this._targetY
                                     });
    }

    close() {
        if (!this._open)
            return;

        this._objInspector.actor.hide();

        this._open = false;
        Tweener.removeTweens(this.actor);

        this.setBorderPaintTarget(null);

        Main.popModal(this._entry);

        Tweener.addTween(this.actor, { time: Math.min(0.5 / St.get_slow_down_factor(), 0.5),
                                       transition: 'easeOutQuad',
                                       y: this._hiddenY,
                                       onComplete: () => {
                                           this.actor.hide();
                                       }
                                     });
    }
};
Signals.addSignalMethods(LookingGlass.prototype);
(uuay)pointerWatcher.js+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GLib, Meta } = imports.gi;
const Mainloop = imports.mainloop;

// We stop polling if the user is idle for more than this amount of time
var IDLE_TIME = 1000;

// This file implements a reasonably efficient system for tracking the position
// of the mouse pointer. We simply query the pointer from the X server in a loop,
// but we turn off the polling when the user is idle.

let _pointerWatcher = null;
function getPointerWatcher() {
    if (_pointerWatcher == null)
        _pointerWatcher = new PointerWatcher();

    return _pointerWatcher;
}

var PointerWatch = class {
    constructor(watcher, interval, callback) {
        this.watcher = watcher;
        this.interval = interval;
        this.callback = callback;
    }

    // remove:
    // remove this watch. This function may safely be called
    // while the callback is executing.
    remove() {
        this.watcher._removeWatch(this);
    }
};

var PointerWatcher = class {
    constructor() {
        this._idleMonitor = Meta.IdleMonitor.get_core();
        this._idleMonitor.add_idle_watch(IDLE_TIME, this._onIdleMonitorBecameIdle.bind(this));
        this._idle = this._idleMonitor.get_idletime() > IDLE_TIME;
        this._watches = [];
        this.pointerX = null;
        this.pointerY = null;
    }

    // addWatch:
    // @interval: hint as to the time resolution needed. When the user is
    //   not idle, the position of the pointer will be queried at least
    //   once every this many milliseconds.
    // @callback to call when the pointer position changes - takes
    //   two arguments, X and Y.
    //
    // Set up a watch on the position of the mouse pointer. Returns a
    // PointerWatch object which has a remove() method to remove the watch.
    addWatch(interval, callback) {
        // Avoid unreliably calling the watch for the current position
        this._updatePointer();

        let watch = new PointerWatch(this, interval, callback);
        this._watches.push(watch);
        this._updateTimeout();
        return watch;
    }

    _removeWatch(watch) {
        for (let i = 0; i < this._watches.length; i++) {
            if (this._watches[i] == watch) {
                this._watches.splice(i, 1);
                this._updateTimeout();
                return;
            }
        }
    }

    _onIdleMonitorBecameActive(monitor) {
        this._idle = false;
        this._updatePointer();
        this._updateTimeout();
    }

    _onIdleMonitorBecameIdle(monitor) {
        this._idle = true;
        this._idleMonitor.add_user_active_watch(this._onIdleMonitorBecameActive.bind(this));
        this._updateTimeout();
    }

    _updateTimeout() {
        if (this._timeoutId) {
            Mainloop.source_remove(this._timeoutId);
            this._timeoutId = 0;
        }

        if (this._idle || this._watches.length == 0)
            return;

        let minInterval = this._watches[0].interval;
        for (let i = 1; i < this._watches.length; i++)
            minInterval = Math.min(this._watches[i].interval, minInterval);

        this._timeoutId = Mainloop.timeout_add(minInterval,
                                               this._onTimeout.bind(this));
        GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._onTimeout');
    }

    _onTimeout() {
        this._updatePointer();
        return GLib.SOURCE_CONTINUE;
    }

    _updatePointer() {
        let [x, y, mods] = global.get_pointer();
        if (this.pointerX == x && this.pointerY == y)
            return;

        this.pointerX = x;
        this.pointerY = y;

        for (let i = 0; i < this._watches.length;) {
            let watch = this._watches[i];
            watch.callback(x, y);
            if (watch == this._watches[i]) // guard against self-removal
                i++;
        }
    }
};
(uuay)sessionMode.jsV// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const FileUtils = imports.misc.fileUtils;
const Params = imports.misc.params;

const Config = imports.misc.config;

const DEFAULT_MODE = 'restrictive';

const _modes = {
    'restrictive': {
        parentMode: null,
        stylesheetName: 'gnome-shell.css',
        hasOverview: false,
        showCalendarEvents: false,
        allowSettings: false,
        allowExtensions: false,
        allowScreencast: false,
        enabledExtensions: [],
        hasRunDialog: false,
        hasWorkspaces: false,
        hasWindows: false,
        hasNotifications: false,
        isLocked: false,
        isGreeter: false,
        isPrimary: false,
        unlockDialog: null,
        components: [],
        panel: {
            left: [],
            center: [],
            right: []
        },
        panelStyle: null
    },

    'gdm': {
        allowExtensions: true,
        hasNotifications: true,
        isGreeter: true,
        isPrimary: true,
        unlockDialog: imports.gdm.loginDialog.LoginDialog,
        components: Config.HAVE_NETWORKMANAGER
            ? ['networkAgent', 'polkitAgent']
            : ['polkitAgent'],
        panel: {
            left: [],
            center: ['dateMenu'],
            right: ['a11y', 'keyboard', 'aggregateMenu']
        },
        panelStyle: 'login-screen'
    },

    'lock-screen': {
        allowExtensions: true,
        isLocked: true,
        isGreeter: undefined,
        unlockDialog: undefined,
        components: ['polkitAgent', 'telepathyClient'],
        panel: {
            left: [],
            center: [],
            right: ['aggregateMenu']
        },
        panelStyle: 'lock-screen'
    },

    'unlock-dialog': {
        allowExtensions: true,
        isLocked: true,
        unlockDialog: undefined,
        components: ['polkitAgent', 'telepathyClient'],
        panel: {
            left: [],
            center: [],
            right: ['a11y', 'keyboard', 'aggregateMenu']
        },
        panelStyle: 'unlock-screen'
    },

    'user': {
        hasOverview: true,
        showCalendarEvents: true,
        allowSettings: true,
        allowExtensions: true,
        allowScreencast: true,
        hasRunDialog: true,
        hasWorkspaces: true,
        hasWindows: true,
        hasNotifications: true,
        isLocked: false,
        isPrimary: true,
        unlockDialog: imports.ui.unlockDialog.UnlockDialog,
        components: Config.HAVE_NETWORKMANAGER ?
                    ['networkAgent', 'polkitAgent', 'telepathyClient',
                     'keyring', 'autorunManager', 'automountManager'] :
                    ['polkitAgent', 'telepathyClient',
                     'keyring', 'autorunManager', 'automountManager'],

        panel: {
            left: ['activities', 'appMenu'],
            center: ['dateMenu'],
            right: ['a11y', 'keyboard', 'aggregateMenu']
        }
    }
};

function _loadMode(file, info) {
    let name = info.get_name();
    let suffix = name.indexOf('.json');
    let modeName = suffix == -1 ? name : name.slice(name, suffix);

    if (_modes.hasOwnProperty(modeName))
        return;

    let fileContent, success, tag, newMode;
    try {
        [success, fileContent, tag] = file.load_contents(null);
        if (fileContent instanceof Uint8Array)
            fileContent = imports.byteArray.toString(fileContent);
        newMode = JSON.parse(fileContent);
    } catch(e) {
        return;
    }

    _modes[modeName] = {};
    let propBlacklist = ['unlockDialog'];
    for (let prop in _modes[DEFAULT_MODE]) {
        if (newMode[prop] !== undefined &&
            propBlacklist.indexOf(prop) == -1)
            _modes[modeName][prop] = newMode[prop];
    }
    _modes[modeName]['isPrimary'] = true;
}

function _loadModes() {
    FileUtils.collectFromDatadirs('modes', false, _loadMode);
}

function listModes() {
    _loadModes();
    let id = Mainloop.idle_add(() => {
        let names = Object.getOwnPropertyNames(_modes);
        for (let i = 0; i < names.length; i++)
            if (_modes[names[i]].isPrimary)
                print(names[i]);
        Mainloop.quit('listModes');
    });
    GLib.Source.set_name_by_id(id, '[gnome-shell] listModes');
    Mainloop.run('listModes');
}

var SessionMode = class {
    constructor() {
        _loadModes();
        let isPrimary = (_modes[global.session_mode] &&
                         _modes[global.session_mode].isPrimary);
        let mode = isPrimary ? global.session_mode : 'user';
        this._modeStack = [mode];
        this._sync();
    }

    pushMode(mode) {
        this._modeStack.push(mode);
        this._sync();
    }

    popMode(mode) {
        if (this.currentMode != mode || this._modeStack.length === 1)
            throw new Error("Invalid SessionMode.popMode");
        this._modeStack.pop();
        this._sync();
    }

    switchMode(to) {
        if (this.currentMode == to)
            return;
        this._modeStack[this._modeStack.length - 1] = to;
        this._sync();
    }

    get currentMode() {
        return this._modeStack[this._modeStack.length - 1];
    }

    _sync() {
        let params = _modes[this.currentMode];
        let defaults;
        if (params.parentMode)
            defaults = Params.parse(_modes[params.parentMode],
                                    _modes[DEFAULT_MODE]);
        else
            defaults = _modes[DEFAULT_MODE];
        params = Params.parse(params, defaults);

        // A simplified version of Lang.copyProperties, handles
        // undefined as a special case for "no change / inherit from previous mode"
        for (let prop in params) {
            if (params[prop] !== undefined)
                this[prop] = params[prop];
        }

        this.emit('updated');
    }
};
Signals.addSignalMethods(SessionMode.prototype);
(uuay)lightbox.js�&// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Shell, St } = imports.gi;
const Signals = imports.signals;

const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

var DEFAULT_FADE_FACTOR = 0.4;
var VIGNETTE_BRIGHTNESS = 0.8;
var VIGNETTE_SHARPNESS = 0.7;

const VIGNETTE_DECLARATIONS = '\
uniform float brightness;\n\
uniform float vignette_sharpness;\n';

const VIGNETTE_CODE = '\
cogl_color_out.a = cogl_color_in.a;\n\
cogl_color_out.rgb = vec3(0.0, 0.0, 0.0);\n\
vec2 position = cogl_tex_coord_in[0].xy - 0.5;\n\
float t = length(2.0 * position);\n\
t = clamp(t, 0.0, 1.0);\n\
float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);\n\
cogl_color_out.a = cogl_color_out.a * (1 - pixel_brightness * brightness);';

var RadialShaderQuad = GObject.registerClass(
class RadialShaderQuad extends Shell.GLSLQuad {
    _init(params) {
        super._init(params);

        this._brightnessLocation = this.get_uniform_location('brightness');
        this._sharpnessLocation = this.get_uniform_location('vignette_sharpness');

        this.brightness = 1.0;
        this.vignetteSharpness = 0.0;
    }

    vfunc_build_pipeline() {
        this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT,
                              VIGNETTE_DECLARATIONS, VIGNETTE_CODE, true);
    }

    get brightness() {
        return this._brightness;
    }

    set brightness(v) {
        this._brightness = v;
        this.set_uniform_float(this._brightnessLocation,
                               1, [this._brightness]);
    }

    get vignetteSharpness() {
        return this._sharpness;
    }

    set vignetteSharpness(v) {
        this._sharpness = v;
        this.set_uniform_float(this._sharpnessLocation,
                               1, [this._sharpness]);
    }
});

/**
 * Lightbox:
 * @container: parent Clutter.Container
 * @params: (optional) additional parameters:
 *           - inhibitEvents: whether to inhibit events for @container
 *           - width: shade actor width
 *           - height: shade actor height
 *           - fadeInTime: seconds used to fade in
 *           - fadeOutTime: seconds used to fade out
 *
 * Lightbox creates a dark translucent "shade" actor to hide the
 * contents of @container, and allows you to specify particular actors
 * in @container to highlight by bringing them above the shade. It
 * tracks added and removed actors in @container while the lightboxing
 * is active, and ensures that all actors are returned to their
 * original stacking order when the lightboxing is removed. (However,
 * if actors are restacked by outside code while the lightboxing is
 * active, the lightbox may later revert them back to their original
 * order.)
 *
 * By default, the shade window will have the height and width of
 * @container and will track any changes in its size. You can override
 * this by passing an explicit width and height in @params.
 */
var Lightbox = class Lightbox {
    constructor(container, params) {
        params = Params.parse(params, { inhibitEvents: false,
                                        width: null,
                                        height: null,
                                        fadeFactor: DEFAULT_FADE_FACTOR,
                                        radialEffect: false,
                                      });

        this._container = container;
        this._children = container.get_children();
        this._fadeFactor = params.fadeFactor;
        this._radialEffect = Clutter.feature_available(Clutter.FeatureFlags.SHADERS_GLSL) && params.radialEffect;
        if (this._radialEffect)
            this.actor = new RadialShaderQuad({ x: 0,
                                                y: 0,
                                                reactive: params.inhibitEvents });
        else
            this.actor = new St.Bin({ x: 0,
                                      y: 0,
                                      opacity: 0,
                                      style_class: 'lightbox',
                                      reactive: params.inhibitEvents });

        container.add_actor(this.actor);
        this.actor.raise_top();
        this.actor.hide();
        this.shown = false;

        this.actor.connect('destroy', this._onDestroy.bind(this));

        if (params.width && params.height) {
            this.actor.width = params.width;
            this.actor.height = params.height;
        } else {
            let constraint = new Clutter.BindConstraint({ source: container,
                                                          coordinate: Clutter.BindCoordinate.ALL });
            this.actor.add_constraint(constraint);
        }

        this._actorAddedSignalId = container.connect('actor-added', this._actorAdded.bind(this));
        this._actorRemovedSignalId = container.connect('actor-removed', this._actorRemoved.bind(this));

        this._highlighted = null;
    }

    _actorAdded(container, newChild) {
        let children = this._container.get_children();
        let myIndex = children.indexOf(this.actor);
        let newChildIndex = children.indexOf(newChild);

        if (newChildIndex > myIndex) {
            // The child was added above the shade (presumably it was
            // made the new top-most child). Move it below the shade,
            // and add it to this._children as the new topmost actor.
            newChild.lower(this.actor);
            this._children.push(newChild);
        } else if (newChildIndex == 0) {
            // Bottom of stack
            this._children.unshift(newChild);
        } else {
            // Somewhere else; insert it into the correct spot
            let prevChild = this._children.indexOf(children[newChildIndex - 1]);
            if (prevChild != -1) // paranoia
                this._children.splice(prevChild + 1, 0, newChild);
        }
    }

    show(fadeInTime) {
        fadeInTime = fadeInTime || 0;

        Tweener.removeTweens(this.actor);
        if (this._radialEffect) {
            Tweener.addTween(this.actor,
                             { brightness: VIGNETTE_BRIGHTNESS,
                               vignetteSharpness: VIGNETTE_SHARPNESS,
                               time: fadeInTime,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this.shown = true;
                                   this.emit('shown');
                               }
                             });
        } else {
            Tweener.addTween(this.actor,
                             { opacity: 255 * this._fadeFactor,
                               time: fadeInTime,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this.shown = true;
                                   this.emit('shown');
                               }
                             });
        }

        this.actor.show();
    }

    hide(fadeOutTime) {
        fadeOutTime = fadeOutTime || 0;

        this.shown = false;
        Tweener.removeTweens(this.actor);
        if (this._radialEffect) {
            Tweener.addTween(this.actor,
                             { brightness: 1.0,
                               vignetteSharpness: 0.0,
                               opacity: 0,
                               time: fadeOutTime,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this.actor.hide();
                               }
                             });
        } else {
            Tweener.addTween(this.actor,
                             { opacity: 0,
                               time: fadeOutTime,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this.actor.hide();
                               }
                             });
        }
    }

    _actorRemoved(container, child) {
        let index = this._children.indexOf(child);
        if (index != -1) // paranoia
            this._children.splice(index, 1);

        if (child == this._highlighted)
            this._highlighted = null;
    }

    /**
     * highlight:
     * @window: actor to highlight
     *
     * Highlights the indicated actor and unhighlights any other
     * currently-highlighted actor. With no arguments or a false/null
     * argument, all actors will be unhighlighted.
     */
    highlight(window) {
        if (this._highlighted == window)
            return;

        // Walk this._children raising and lowering actors as needed.
        // Things get a little tricky if the to-be-raised and
        // to-be-lowered actors were originally adjacent, in which
        // case we may need to indicate some *other* actor as the new
        // sibling of the to-be-lowered one.

        let below = this.actor;
        for (let i = this._children.length - 1; i >= 0; i--) {
            if (this._children[i] == window)
                this._children[i].raise_top();
            else if (this._children[i] == this._highlighted)
                this._children[i].lower(below);
            else
                below = this._children[i];
        }

        this._highlighted = window;
    }

    /**
     * destroy:
     *
     * Destroys the lightbox.
     */
    destroy() {
        this.actor.destroy();
    }

    /**
     * _onDestroy:
     *
     * This is called when the lightbox' actor is destroyed, either
     * by destroying its container or by explicitly calling this.destroy().
     */
    _onDestroy() {
        this._container.disconnect(this._actorAddedSignalId);
        this._container.disconnect(this._actorRemovedSignalId);

        this.highlight(null);
    }
};
Signals.addSignalMethods(Lightbox.prototype);
(uuay)barLevel.jst"/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

const { Atk, Clutter, St } = imports.gi;
const Signals = imports.signals;

var BarLevel = class {
    constructor(value, params) {
        if (isNaN(value))
            // Avoid spreading NaNs around
            throw TypeError('The bar level value must be a number');
        this._maxValue = 1;
        this._value = Math.max(Math.min(value, this._maxValue), 0);
        this._overdriveStart = 1;
        this._barLevelWidth = 0;

        if (params == undefined)
            params = {}

        this.actor = new St.DrawingArea({ styleClass: params['styleClass'] || 'barlevel',
                                          can_focus: params['canFocus'] || false,
                                          reactive: params['reactive'] || false,
                                          accessible_role: params['accessibleRole'] || Atk.Role.LEVEL_BAR });
        this.actor.connect('repaint', this._barLevelRepaint.bind(this));
        this.actor.connect('allocation-changed', (actor, box) => {
            this._barLevelWidth = box.get_width();
        });

        this._customAccessible = St.GenericAccessible.new_for_actor(this.actor);
        this.actor.set_accessible(this._customAccessible);

        this._customAccessible.connect('get-current-value', this._getCurrentValue.bind(this));
        this._customAccessible.connect('get-minimum-value', this._getMinimumValue.bind(this));
        this._customAccessible.connect('get-maximum-value', this._getMaximumValue.bind(this));
        this._customAccessible.connect('set-current-value', this._setCurrentValue.bind(this));

        this.connect('value-changed', this._valueChanged.bind(this));
    }

    setValue(value) {
        if (isNaN(value))
            throw TypeError('The bar level value must be a number');

        this._value = Math.max(Math.min(value, this._maxValue), 0);
        this.actor.queue_repaint();
    }

    setMaximumValue(value) {
        if (isNaN(value))
            throw TypeError('The bar level max value must be a number');

        this._maxValue = Math.max(value, 1);
        this._overdriveStart = Math.min(this._overdriveStart, this._maxValue);
        this.actor.queue_repaint();
    }

    setOverdriveStart(value) {
        if (isNaN(value))
            throw TypeError('The overdrive limit value must be a number');
        if (value > this._maxValue)
            throw new Error(`Tried to set overdrive value to ${value}, ` +
                `which is a number greater than the maximum allowed value ${this._maxValue}`);

        this._overdriveStart = value;
        this._value = Math.max(Math.min(value, this._maxValue), 0);
        this.actor.queue_repaint();
    }

    _barLevelRepaint(area) {
        let cr = area.get_context();
        let themeNode = area.get_theme_node();
        let [width, height] = area.get_surface_size();

        let barLevelHeight = themeNode.get_length('-barlevel-height');
        let barLevelBorderRadius = Math.min(width, barLevelHeight) / 2;
        let fgColor = themeNode.get_foreground_color();

        let barLevelColor = themeNode.get_color('-barlevel-background-color');
        let barLevelActiveColor = themeNode.get_color('-barlevel-active-background-color');
        let barLevelOverdriveColor = themeNode.get_color('-barlevel-overdrive-color');

        let barLevelBorderWidth = Math.min(themeNode.get_length('-barlevel-border-width'), 1);
        let [hasBorderColor, barLevelBorderColor] =
            themeNode.lookup_color('-barlevel-border-color', false);
        if (!hasBorderColor)
            barLevelBorderColor = barLevelColor;
        let [hasActiveBorderColor, barLevelActiveBorderColor] =
            themeNode.lookup_color('-barlevel-active-border-color', false);
        if (!hasActiveBorderColor)
            barLevelActiveBorderColor = barLevelActiveColor;
        let [hasOverdriveBorderColor, barLevelOverdriveBorderColor] =
            themeNode.lookup_color('-barlevel-overdrive-border-color', false);
        if (!hasOverdriveBorderColor)
            barLevelOverdriveBorderColor = barLevelOverdriveColor;

        const TAU = Math.PI * 2;

        let endX = 0;
        if (this._maxValue > 0)
            endX = barLevelBorderRadius + (width - 2 * barLevelBorderRadius) * this._value / this._maxValue;

        let overdriveSeparatorX = barLevelBorderRadius + (width - 2 * barLevelBorderRadius) * this._overdriveStart / this._maxValue;
        let overdriveActive = this._overdriveStart !== this._maxValue;
        let overdriveSeparatorWidth = 0;
        if (overdriveActive)
            overdriveSeparatorWidth = themeNode.get_length('-barlevel-overdrive-separator-width');

        /* background bar */
        cr.arc(width - barLevelBorderRadius - barLevelBorderWidth, height / 2, barLevelBorderRadius, TAU * 3 / 4, TAU * 1 / 4);
        cr.lineTo(endX, (height + barLevelHeight) / 2);
        cr.lineTo(endX, (height - barLevelHeight) / 2);
        cr.lineTo(width - barLevelBorderRadius - barLevelBorderWidth, (height - barLevelHeight) / 2);
        Clutter.cairo_set_source_color(cr, barLevelColor);
        cr.fillPreserve();
        Clutter.cairo_set_source_color(cr, barLevelBorderColor);
        cr.setLineWidth(barLevelBorderWidth);
        cr.stroke();

        /* normal progress bar */
        let x = Math.min(endX, overdriveSeparatorX - overdriveSeparatorWidth / 2);
        cr.arc(barLevelBorderRadius + barLevelBorderWidth, height / 2, barLevelBorderRadius, TAU * 1 / 4, TAU * 3 / 4);
        cr.lineTo(x, (height - barLevelHeight) / 2);
        cr.lineTo(x, (height + barLevelHeight) / 2);
        cr.lineTo(barLevelBorderRadius + barLevelBorderWidth, (height + barLevelHeight) / 2);
        if (this._value > 0)
          Clutter.cairo_set_source_color(cr, barLevelActiveColor);
        cr.fillPreserve();
        Clutter.cairo_set_source_color(cr, barLevelActiveBorderColor);
        cr.setLineWidth(barLevelBorderWidth);
        cr.stroke();

        /* overdrive progress barLevel */
        x = Math.min(endX, overdriveSeparatorX) + overdriveSeparatorWidth / 2;
        if (this._value > this._overdriveStart) {
            cr.moveTo(x, (height - barLevelHeight) / 2);
            cr.lineTo(endX, (height - barLevelHeight) / 2);
            cr.lineTo(endX, (height + barLevelHeight) / 2);
            cr.lineTo(x, (height + barLevelHeight) / 2);
            cr.lineTo(x, (height - barLevelHeight) / 2);
            Clutter.cairo_set_source_color(cr, barLevelOverdriveColor);
            cr.fillPreserve();
            Clutter.cairo_set_source_color(cr, barLevelOverdriveBorderColor);
            cr.setLineWidth(barLevelBorderWidth);
            cr.stroke();
        }

        /* end progress bar arc */
        if (this._value > 0) {
          if (this._value <= this._overdriveStart)
              Clutter.cairo_set_source_color(cr, barLevelActiveColor);
          else
              Clutter.cairo_set_source_color(cr, barLevelOverdriveColor);
          cr.arc(endX, height / 2, barLevelBorderRadius, TAU * 3 / 4, TAU * 1 / 4);
          cr.lineTo(Math.floor(endX), (height + barLevelHeight) / 2);
          cr.lineTo(Math.floor(endX), (height - barLevelHeight) / 2);
          cr.lineTo(endX, (height - barLevelHeight) / 2);
          cr.fillPreserve();
          cr.setLineWidth(barLevelBorderWidth);
          cr.stroke();
        }

        /* draw overdrive separator */
        if (overdriveActive) {
            cr.moveTo(overdriveSeparatorX - overdriveSeparatorWidth / 2, (height - barLevelHeight) / 2);
            cr.lineTo(overdriveSeparatorX + overdriveSeparatorWidth / 2, (height - barLevelHeight) / 2);
            cr.lineTo(overdriveSeparatorX + overdriveSeparatorWidth / 2, (height + barLevelHeight) / 2);
            cr.lineTo(overdriveSeparatorX - overdriveSeparatorWidth / 2, (height + barLevelHeight) / 2);
            cr.lineTo(overdriveSeparatorX - overdriveSeparatorWidth / 2, (height - barLevelHeight) / 2);
            if (this._value <= this._overdriveStart)
                Clutter.cairo_set_source_color(cr, fgColor);
            else
                Clutter.cairo_set_source_color(cr, barLevelColor);
            cr.fill();
        }

        cr.$dispose();
    }

    _getCurrentValue(actor) {
        return this._value;
    }

    _getOverdriveStart(actor) {
        return this._overdriveStart;
    }

    _getMinimumValue(actor) {
        return 0;
    }

    _getMaximumValue(actor) {
        return this._maxValue;
    }

    _setCurrentValue(actor, value) {
        this._value = value;
    }

    _valueChanged(barLevel, value, property) {
        this._customAccessible.notify("accessible-value");
    }

    get value() {
        return this._value;
    }
};
Signals.addSignalMethods(BarLevel.prototype);
(uuay)grabHelper.js�'// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, St } = imports.gi;

const Main = imports.ui.main;
const Params = imports.misc.params;

let _capturedEventId = 0;
let _grabHelperStack = [];
function _onCapturedEvent(actor, event) {
    let grabHelper = _grabHelperStack[_grabHelperStack.length - 1];
    return grabHelper.onCapturedEvent(event);
}

function _pushGrabHelper(grabHelper) {
    _grabHelperStack.push(grabHelper);

    if (_capturedEventId == 0)
        _capturedEventId = global.stage.connect('captured-event', _onCapturedEvent);
}

function _popGrabHelper(grabHelper) {
    let poppedHelper = _grabHelperStack.pop();
    if (poppedHelper != grabHelper)
        throw new Error("incorrect grab helper pop");

    if (_grabHelperStack.length == 0) {
        global.stage.disconnect(_capturedEventId);
        _capturedEventId = 0;
    }
}

// GrabHelper:
// @owner: the actor that owns the GrabHelper
// @params: optional parameters to pass to Main.pushModal()
//
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
// associated with a set of actors.
//
// Note that the grab can be automatically dropped at any time by the user, and
// your code just needs to deal with it; you shouldn't adjust behavior directly
// after you call ungrab(), but instead pass an 'onUngrab' callback when you
// call grab().
var GrabHelper = class GrabHelper {
    constructor(owner, params) {
        this._owner = owner;
        this._modalParams = params;

        this._grabStack = [];

        this._actors = [];
        this._ignoreUntilRelease = false;

        this._modalCount = 0;
    }

    // addActor:
    // @actor: an actor
    //
    // Adds @actor to the set of actors that are allowed to process events
    // during a grab.
    addActor(actor) {
        actor.__grabHelperDestroyId = actor.connect('destroy', () => {
            this.removeActor(actor);
        });
        this._actors.push(actor);
    }

    // removeActor:
    // @actor: an actor
    //
    // Removes @actor from the set of actors that are allowed to
    // process events during a grab.
    removeActor(actor) {
        let index = this._actors.indexOf(actor);
        if (index != -1)
            this._actors.splice(index, 1);
        if (actor.__grabHelperDestroyId) {
            actor.disconnect(actor.__grabHelperDestroyId);
            delete actor.__grabHelperDestroyId;
        }
    }

    _isWithinGrabbedActor(actor) {
        let currentActor = this.currentGrab.actor;
        while (actor) {
            if (this._actors.indexOf(actor) != -1)
                return true;
            if (actor == currentActor)
                return true;
            actor = actor.get_parent();
        }
        return false;
    }

    get currentGrab() {
        return this._grabStack[this._grabStack.length - 1] || {};
    }

    get grabbed() {
        return this._grabStack.length > 0;
    }

    get grabStack() {
        return this._grabStack;
    }

    _findStackIndex(actor) {
        if (!actor)
            return -1;

        for (let i = 0; i < this._grabStack.length; i++) {
            if (this._grabStack[i].actor === actor)
                return i;
        }
        return -1;
    }

    _actorInGrabStack(actor) {
        while (actor) {
            let idx = this._findStackIndex(actor);
            if (idx >= 0)
                return idx;
            actor = actor.get_parent();
        }
        return -1;
    }

    isActorGrabbed(actor) {
        return this._findStackIndex(actor) >= 0;
    }

    // grab:
    // @params: A bunch of parameters, see below
    //
    // The general effect of a "grab" is to ensure that the passed in actor
    // and all actors inside the grab get exclusive control of the mouse and
    // keyboard, with the grab automatically being dropped if the user tries
    // to dismiss it. The actor is passed in through @params.actor.
    //
    // grab() can be called multiple times, with the scope of the grab being
    // changed to a different actor every time. A nested grab does not have
    // to have its grabbed actor inside the parent grab actors.
    //
    // Grabs can be automatically dropped if the user tries to dismiss it
    // in one of two ways: the user clicking outside the currently grabbed
    // actor, or the user typing the Escape key.
    //
    // If the user clicks outside the grabbed actors, and the clicked on
    // actor is part of a previous grab in the stack, grabs will be popped
    // until that grab is active. However, the click event will not be
    // replayed to the actor.
    //
    // If the user types the Escape key, one grab from the grab stack will
    // be popped.
    //
    // When a grab is popped by user interacting as described above, if you
    // pass a callback as @params.onUngrab, it will be called with %true.
    //
    // If @params.focus is not null, we'll set the key focus directly
    // to that actor instead of navigating in @params.actor. This is for
    // use cases like menus, where we want to grab the menu actor, but keep
    // focus on the clicked on menu item.
    grab(params) {
        params = Params.parse(params, { actor: null,
                                        focus: null,
                                        onUngrab: null });

        let focus = global.stage.key_focus;
        let hadFocus = focus && this._isWithinGrabbedActor(focus);
        let newFocus = params.actor;

        if (this.isActorGrabbed(params.actor))
            return true;

        params.savedFocus = focus;

        if (!this._takeModalGrab())
            return false;

        this._grabStack.push(params);

        if (params.focus) {
            params.focus.grab_key_focus();
        } else if (newFocus && hadFocus) {
            if (!newFocus.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
                newFocus.grab_key_focus();
        }

        return true;
    }

    _takeModalGrab() {
        let firstGrab = (this._modalCount == 0);
        if (firstGrab) {
            if (!Main.pushModal(this._owner, this._modalParams))
                return false;

            _pushGrabHelper(this);
        }

        this._modalCount++;
        return true;
    }

    _releaseModalGrab() {
        this._modalCount--;
        if (this._modalCount > 0)
            return;

        _popGrabHelper(this);

        this._ignoreUntilRelease = false;

        Main.popModal(this._owner);
        global.sync_pointer();
    }

    // ignoreRelease:
    //
    // Make sure that the next button release event evaluated by the
    // capture event handler returns false. This is designed for things
    // like the ComboBoxMenu that go away on press, but need to eat
    // the next release event.
    ignoreRelease() {
        this._ignoreUntilRelease = true;
    }

    // ungrab:
    // @params: The parameters for the grab; see below.
    //
    // Pops @params.actor from the grab stack, potentially dropping
    // the grab. If the actor is not on the grab stack, this call is
    // ignored with no ill effects.
    //
    // If the actor is not at the top of the grab stack, grabs are
    // popped until the grabbed actor is at the top of the grab stack.
    // The onUngrab callback for every grab is called for every popped
    // grab with the parameter %false.
    ungrab(params) {
        params = Params.parse(params, { actor: this.currentGrab.actor,
                                        isUser: false });

        let grabStackIndex = this._findStackIndex(params.actor);
        if (grabStackIndex < 0)
            return;

        let focus = global.stage.key_focus;
        let hadFocus = focus && this._isWithinGrabbedActor(focus);

        let poppedGrabs = this._grabStack.slice(grabStackIndex);
        // "Pop" all newly ungrabbed actors off the grab stack
        // by truncating the array.
        this._grabStack.length = grabStackIndex;

        for (let i = poppedGrabs.length - 1; i >= 0; i--) {
            let poppedGrab = poppedGrabs[i];

            if (poppedGrab.onUngrab)
                poppedGrab.onUngrab(params.isUser);

            this._releaseModalGrab();
        }

        if (hadFocus) {
            let poppedGrab = poppedGrabs[0];
            if (poppedGrab.savedFocus)
                poppedGrab.savedFocus.grab_key_focus();
        }
    }

    onCapturedEvent(event) {
        let type = event.type();

        if (type == Clutter.EventType.KEY_PRESS &&
            event.get_key_symbol() == Clutter.KEY_Escape) {
            this.ungrab({ isUser: true });
            return Clutter.EVENT_STOP;
        }

        let motion = type == Clutter.EventType.MOTION;
        let press = type == Clutter.EventType.BUTTON_PRESS;
        let release = type == Clutter.EventType.BUTTON_RELEASE;
        let button = press || release;

        let touchUpdate = type == Clutter.EventType.TOUCH_UPDATE;
        let touchBegin = type == Clutter.EventType.TOUCH_BEGIN;
        let touchEnd = type == Clutter.EventType.TOUCH_END;
        let touch = touchUpdate || touchBegin || touchEnd;

        if (touch && !global.display.is_pointer_emulating_sequence (event.get_event_sequence()))
            return Clutter.EVENT_PROPAGATE;

        if (this._ignoreUntilRelease && (motion || release || touch)) {
            if (release || touchEnd)
                this._ignoreUntilRelease = false;
            return Clutter.EVENT_STOP;
        }

        if (this._isWithinGrabbedActor(event.get_source()))
            return Clutter.EVENT_PROPAGATE;

        if (Main.keyboard.shouldTakeEvent(event))
            return Clutter.EVENT_PROPAGATE;

        if (button || touchBegin) {
            // If we have a press event, ignore the next
            // motion/release events.
            if (press || touchBegin)
                this._ignoreUntilRelease = true;

            let i = this._actorInGrabStack(event.get_source()) + 1;
            this.ungrab({ actor: this._grabStack[i].actor, isUser: true });
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_STOP;
    }
};
(uuay)screencast.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib, Meta, Shell } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;

const { loadInterfaceXML } = imports.misc.fileUtils;

const ScreencastIface = loadInterfaceXML('org.gnome.Shell.Screencast');

var ScreencastService = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreencastIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell/Screencast');

        Gio.DBus.session.own_name('org.gnome.Shell.Screencast', Gio.BusNameOwnerFlags.REPLACE, null, null);

        this._recorders = new Map();

        this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
    }

    get isRecording() {
        return this._recorders.size > 0;
    }

    _ensureRecorderForSender(sender) {
        let recorder = this._recorders.get(sender);
        if (!recorder) {
            recorder = new Shell.Recorder({ stage: global.stage,
                                            display: global.display });
            recorder._watchNameId =
                Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
                                   this._onNameVanished.bind(this));
            this._recorders.set(sender, recorder);
            this.emit('updated');
        }
        return recorder;
    }

    _sessionUpdated() {
        if (Main.sessionMode.allowScreencast)
            return;

        for (let sender of this._recorders.keys())
            this._stopRecordingForSender(sender);
    }

    _onNameVanished(connection, name) {
        this._stopRecordingForSender(name);
    }

    _stopRecordingForSender(sender, closeNow=false) {
        let recorder = this._recorders.get(sender);
        if (!recorder)
            return false;

        Gio.bus_unwatch_name(recorder._watchNameId);
        if (closeNow)
            recorder.close_now();
        else
            recorder.close();
        this._recorders.delete(sender);
        this.emit('updated');

        let connection = this._dbusImpl.get_connection();
        let info = this._dbusImpl.get_info();
        connection.emit_signal(sender,
            this._dbusImpl.get_object_path(),
            info ? info.name : null,
            'Stopped',
            null);

        return true;
    }

    _applyOptionalParameters(recorder, options) {
        for (let option in options)
            options[option] = options[option].deep_unpack();

        if (options['pipeline'])
            recorder.set_pipeline(options['pipeline']);
        if (options['framerate'])
            recorder.set_framerate(options['framerate']);
        if ('draw-cursor' in options)
            recorder.set_draw_cursor(options['draw-cursor']);
    }

    _ensureResourceScaleChangedHandler() {
        if (this._resourceScaleChangedHandlerId)
            return;

        this._resourceScaleChangedHandlerId =
            global.stage.connect('notify::resource-scale',
                () => {
                    for (let sender of this._recorders.keys()) {
                        let recorder = this._recorders.get(sender);

                        if (!recorder.is_recording())
                            continue;

                        this._stopRecordingForSender(sender, true);
                    }
                });
    }

    _ensureMonitorsChangedHandler() {
        if (this._monitorsChangedHandlerId)
            return;

        this._monitorsChangedHandlerId = Main.layoutManager.connect('monitors-changed',
            () => {
                for (let sender of this._recorders.keys()) {
                    let recorder = this._recorders.get(sender);

                    if (!recorder.is_recording())
                        continue;

                    let geometry = recorder._geometry;
                    let screenWidth = global.screen_width;
                    let screenHeight = global.screen_height;

                    if (recorder._isAreaScreecast) {
                        if (geometry.x + geometry.width > screenWidth ||
                            geometry.y + geometry.height > screenHeight)
                            this._stopRecordingForSender(sender, true);
                    } else {
                        if (geometry.width != screenWidth ||
                            geometry.height != screenHeight)
                            this._stopRecordingForSender(sender, true);
                    }
                }
            });
    }

    ScreencastAsync(params, invocation) {
        let returnValue = [false, ''];
        if (!Main.sessionMode.allowScreencast ||
            this._lockdownSettings.get_boolean('disable-save-to-disk')) {
            invocation.return_value(GLib.Variant.new('(bs)', returnValue));
            return;
        }

        let sender = invocation.get_sender();
        let recorder = this._ensureRecorderForSender(sender);
        if (!recorder.is_recording()) {
            let [fileTemplate, options] = params;

            recorder.set_file_template(fileTemplate);
            this._applyOptionalParameters(recorder, options);
            let [success, fileName] = recorder.record();
            returnValue = [success, fileName ? fileName : ''];
            if (success) {
                recorder._isAreaScreecast = false;
                recorder._geometry =
                    new Meta.Rectangle({
                        x: 0,
                        y: 0,
                        width: global.screen_width,
                        height: global.screen_height
                    });
                this._ensureResourceScaleChangedHandler();
                this._ensureMonitorsChangedHandler();
            } else {
                this._stopRecordingForSender(sender);
            }
        }

        invocation.return_value(GLib.Variant.new('(bs)', returnValue));
    }

    ScreencastAreaAsync(params, invocation) {
        let returnValue = [false, ''];
        if (!Main.sessionMode.allowScreencast ||
            this._lockdownSettings.get_boolean('disable-save-to-disk')) {
            invocation.return_value(GLib.Variant.new('(bs)', returnValue));
            return;
        }

        let sender = invocation.get_sender();
        let recorder = this._ensureRecorderForSender(sender);

        if (!recorder.is_recording()) {
            let [x, y, width, height, fileTemplate, options] = params;

            if (x < 0 || y < 0 ||
                width <= 0 || height <= 0 ||
                x + width > global.screen_width ||
                y + height > global.screen_height) {
                invocation.return_error_literal(Gio.IOErrorEnum,
                                                Gio.IOErrorEnum.CANCELLED,
                                                "Invalid params");
                return;
            }

            recorder.set_file_template(fileTemplate);
            recorder.set_area(x, y, width, height);
            this._applyOptionalParameters(recorder, options);
            let [success, fileName] = recorder.record();
            returnValue = [success, fileName ? fileName : ''];
            if (success) {
                recorder._isAreaScreecast = true;
                recorder._geometry =
                    new Meta.Rectangle({
                        x: x,
                        y: y,
                        width: width,
                        height: height
                    });
                this._ensureResourceScaleChangedHandler();
                this._ensureMonitorsChangedHandler();
            } else {
                this._stopRecordingForSender(sender);
            }
        }

        invocation.return_value(GLib.Variant.new('(bs)', returnValue));
    }

    StopScreencastAsync(params, invocation) {
        let success = this._stopRecordingForSender(invocation.get_sender());
        invocation.return_value(GLib.Variant.new('(b)', [success]));
    }
};
Signals.addSignalMethods(ScreencastService.prototype);
(uuay)authPrompt.js�\// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;

const Animation = imports.ui.animation;
const AuthList = imports.gdm.authList;
const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
const Meta = imports.gi.Meta;
const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const UserWidget = imports.ui.userWidget;

var DEFAULT_BUTTON_WELL_ICON_SIZE = 16;
var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1.0;
var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 0.3;

var MESSAGE_FADE_OUT_ANIMATION_TIME = 0.5;

var AuthPromptMode = {
    UNLOCK_ONLY: 0,
    UNLOCK_OR_LOG_IN: 1
};

var AuthPromptStatus = {
    NOT_VERIFYING: 0,
    VERIFYING: 1,
    VERIFICATION_FAILED: 2,
    VERIFICATION_SUCCEEDED: 3
};

var BeginRequestType = {
    PROVIDE_USERNAME: 0,
    DONT_PROVIDE_USERNAME: 1
};

var AuthPrompt = class {
    constructor(gdmClient, mode) {
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;

        this._gdmClient = gdmClient;
        this._mode = mode;

        this._idleMonitor = Meta.IdleMonitor.get_core();

        let reauthenticationOnly;
        if (this._mode == AuthPromptMode.UNLOCK_ONLY)
            reauthenticationOnly = true;
        else if (this._mode == AuthPromptMode.UNLOCK_OR_LOG_IN)
            reauthenticationOnly = false;

        this._userVerifier = new GdmUtil.ShellUserVerifier(this._gdmClient, { reauthenticationOnly: reauthenticationOnly });

        this._userVerifier.connect('ask-question', this._onAskQuestion.bind(this));
        this._userVerifier.connect('show-message', this._onShowMessage.bind(this));
        this._userVerifier.connect('show-choice-list', this._onShowChoiceList.bind(this));
        this._userVerifier.connect('verification-failed', this._onVerificationFailed.bind(this));
        this._userVerifier.connect('verification-complete', this._onVerificationComplete.bind(this));
        this._userVerifier.connect('reset', this._onReset.bind(this));
        this._userVerifier.connect('smartcard-status-changed', this._onSmartcardStatusChanged.bind(this));
        this._userVerifier.connect('ovirt-user-authenticated', this._onOVirtUserAuthenticated.bind(this));
        this.smartcardDetected = this._userVerifier.smartcardDetected;

        this.connect('next', () => {
                this.updateSensitivity(false);
                if (this._queryingService) {
                    this.startSpinning();
                    this._userVerifier.answerQuery(this._queryingService, this._entry.text);
                } else {
                    this._preemptiveAnswer = this._entry.text;

                    if (this._preemptiveAnswerWatchId) {
                        this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
                        this._preemptiveAnswerWatchId = 0;
                    }
                }
            });

        this.actor = new St.BoxLayout({ style_class: 'login-dialog-prompt-layout',
                                        vertical: true });
        this.actor.connect('destroy', this._onDestroy.bind(this));
        this.actor.connect('key-press-event', (actor, event) => {
                if (event.get_key_symbol() == Clutter.KEY_Escape)
                    this.cancel();
                return Clutter.EVENT_PROPAGATE;
            });

        this._userWell = new St.Bin({ x_fill: true,
                                      x_align: St.Align.START });
        this.actor.add(this._userWell,
                       { x_align: St.Align.START,
                         x_fill: true,
                         y_fill: true,
                         expand: true });
        this._label = new St.Label({ style_class: 'login-dialog-prompt-label' });

        this.actor.add(this._label,
                       { expand: true,
                         x_fill: false,
                         y_fill: true,
                         x_align: St.Align.START });
        this._entry = new St.Entry({ style_class: 'login-dialog-prompt-entry',
                                     can_focus: true });
        ShellEntry.addContextMenu(this._entry, { isPassword: true, actionMode: Shell.ActionMode.NONE });

        this.actor.add(this._entry,
                       { expand: true,
                         x_fill: true,
                         y_fill: false,
                         x_align: St.Align.START });

        this._entry.grab_key_focus();

        this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
        this.actor.add_child(this._capsLockWarningLabel);

        this._timedLoginIndicator = new St.Bin({ style_class: 'login-dialog-timed-login-indicator',
                                                 scale_x: 0 });

        this.actor.add(this._timedLoginIndicator);

        this._authList = new AuthList.AuthList();
        this._authList.connect('activate', (list, key) => {
            this._authList.actor.reactive = false;
            Tweener.addTween(this._authList.actor,
                             { opacity: 0,
                               time: MESSAGE_FADE_OUT_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this._authList.clear();
                                   this._authList.actor.hide();
                                   this._userVerifier.selectChoice(this._queryingService, key);

                               }
                             });
        });
        this._authList.actor.hide();
        this.actor.add(this._authList.actor,
                       { expand: true,
                         x_fill: true,
                         y_fill: false,
                         x_align: St.Align.START });

        this._message = new St.Label({ opacity: 0,
                                       styleClass: 'login-dialog-message' });
        this._message.clutter_text.line_wrap = true;
        this._message.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this.actor.add(this._message, { x_fill: false, x_align: St.Align.START, y_align: St.Align.START });

        this._buttonBox = new St.BoxLayout({ style_class: 'login-dialog-button-box',
                                             vertical: false });
        this.actor.add(this._buttonBox,
                       { expand:  true,
                         x_align: St.Align.MIDDLE,
                         y_align: St.Align.END });

        this._defaultButtonWell = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        this._defaultButtonWellActor = null;

        this._initButtons();

        this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE);
        this._spinner.actor.opacity = 0;
        this._spinner.actor.show();
        this._defaultButtonWell.add_child(this._spinner.actor);
    }

    showTimedLoginIndicator(time) {
        let hold = new Batch.Hold();

        this.hideTimedLoginIndicator();

        let startTime = GLib.get_monotonic_time();

        this._timedLoginTimeoutId = GLib.timeout_add (GLib.PRIORITY_DEFAULT, 33,
            () => {
                let currentTime = GLib.get_monotonic_time();
                let elapsedTime = (currentTime - startTime) / GLib.USEC_PER_SEC;
                this._timedLoginIndicator.scale_x = elapsedTime / time;
                if (elapsedTime >= time) {
                    this._timedLoginTimeoutId = 0;
                    hold.release();
                    return GLib.SOURCE_REMOVE;
                }

                return GLib.SOURCE_CONTINUE;
            });

        GLib.Source.set_name_by_id(this._timedLoginTimeoutId, '[gnome-shell] this._timedLoginTimeoutId');

        return hold;
    }

    hideTimedLoginIndicator() {
        if (this._timedLoginTimeoutId) {
            GLib.source_remove(this._timedLoginTimeoutId);
            this._timedLoginTimeoutId = 0;
        }
        this._timedLoginIndicator.scale_x = 0.;
    }

    _onDestroy() {
        if (this._preemptiveAnswerWatchId) {
            this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
            this._preemptiveAnswerWatchId = 0;
        }

        this._userVerifier.destroy();
        this._userVerifier = null;
    }

    _initButtons() {
        this.cancelButton = new St.Button({ style_class: 'modal-dialog-button button',
                                            button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                            reactive: true,
                                            can_focus: true,
                                            label: _("Cancel") });
        this.cancelButton.connect('clicked', () => { this.cancel(); });
        this._buttonBox.add(this.cancelButton,
                            { expand: false,
                              x_fill: false,
                              y_fill: false,
                              x_align: St.Align.START,
                              y_align: St.Align.END });

        this._buttonBox.add(this._defaultButtonWell,
                            { expand: true,
                              x_fill: false,
                              y_fill: false,
                              x_align: St.Align.END,
                              y_align: St.Align.MIDDLE });
        this.nextButton = new St.Button({ style_class: 'modal-dialog-button button',
                                          button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
                                          reactive: true,
                                          can_focus: true,
                                          label: _("Next") });
        this.nextButton.connect('clicked', () => { this.emit('next'); });
        this.nextButton.add_style_pseudo_class('default');
        this._buttonBox.add(this.nextButton,
                            { expand: false,
                              x_fill: false,
                              y_fill: false,
                              x_align: St.Align.END,
                              y_align: St.Align.END });

        this._updateNextButtonSensitivity(this._entry.text.length > 0);

        this._entry.clutter_text.connect('text-changed', () => {
            if (!this._userVerifier.hasPendingMessages && this._queryingService && !this._preemptiveAnswer)
                this._fadeOutMessage();

            this._updateNextButtonSensitivity(this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING);
        });
        this._entry.clutter_text.connect('activate', () => {
            if (this.nextButton.reactive)
                this.emit('next');
        });
    }

    _onAskQuestion(verifier, serviceName, question, passwordChar) {
        if (this._queryingService)
            this.clear();

        this._queryingService = serviceName;
        if (this._preemptiveAnswer) {
            this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
            this._preemptiveAnswer = null;
            return;
        }
        this.setPasswordChar(passwordChar);
        this.setQuestion(question);

        if (passwordChar) {
            if (this._userVerifier.reauthenticating)
                this.nextButton.label = _("Unlock");
            else
                this.nextButton.label = C_("button", "Sign In");
        } else {
            this.nextButton.label = _("Next");
        }

        this.updateSensitivity(true);
        this.emit('prompted');
    }

    _onShowChoiceList(userVerifier, serviceName, promptMessage, choiceList) {
        if (this._queryingService)
            this.clear();

        this._queryingService = serviceName;

        if (this._preemptiveAnswer)
            this._preemptiveAnswer = null;

        this.nextButton.label = _("Next");
        this.setChoiceList(promptMessage, choiceList);
        this.updateSensitivity(true);
        this.emit('prompted');
    }

    _onOVirtUserAuthenticated() {
        if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
            this.reset();
    }

    _onSmartcardStatusChanged() {
        this.smartcardDetected = this._userVerifier.smartcardDetected;

        // Most of the time we want to reset if the user inserts or removes
        // a smartcard. Smartcard insertion "preempts" what the user was
        // doing, and smartcard removal aborts the preemption.
        // The exceptions are: 1) Don't reset on smartcard insertion if we're already verifying
        //                        with a smartcard
        //                     2) Don't reset if we've already succeeded at verification and
        //                        the user is getting logged in.
        if (this._userVerifier.serviceIsDefault(GdmUtil.SMARTCARD_SERVICE_NAME) &&
            this.verificationStatus == AuthPromptStatus.VERIFYING &&
            this.smartcardDetected)
            return;

        if (this.verificationStatus != AuthPromptStatus.VERIFICATION_SUCCEEDED)
            this.reset();
    }

    _onShowMessage(userVerifier, message, type) {
        this.setMessage(message, type);
        this.emit('prompted');
    }

    _onVerificationFailed(userVerifier, canRetry) {
        this._queryingService = null;
        this.clear();

        this.updateSensitivity(canRetry);
        this.setActorInDefaultButtonWell(null);
        this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
    }

    _onVerificationComplete() {
        this.setActorInDefaultButtonWell(null);
        this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED;
        this.cancelButton.reactive = false;
    }

    _onReset() {
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
        this.reset();
    }

    addActorToDefaultButtonWell(actor) {
        this._defaultButtonWell.add_child(actor);
    }

    setActorInDefaultButtonWell(actor, animate) {
        if (!this._defaultButtonWellActor &&
            !actor)
            return;

        let oldActor = this._defaultButtonWellActor;

        if (oldActor)
            Tweener.removeTweens(oldActor);

        let wasSpinner;
        if (oldActor == this._spinner.actor)
            wasSpinner = true;
        else
            wasSpinner = false;

        let isSpinner;
        if (actor == this._spinner.actor)
            isSpinner = true;
        else
            isSpinner = false;

        if (this._defaultButtonWellActor != actor && oldActor) {
            if (!animate) {
                oldActor.opacity = 0;

                if (wasSpinner) {
                    if (this._spinner)
                        this._spinner.stop();
                }
            } else {
                Tweener.addTween(oldActor,
                                 { opacity: 0,
                                   time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                                   delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                                   transition: 'linear',
                                   onCompleteScope: this,
                                   onComplete() {
                                      if (wasSpinner) {
                                          if (this._spinner)
                                              this._spinner.stop();
                                      }
                                   }
                                 });
            }
        }

        if (actor) {
            if (isSpinner)
                this._spinner.play();

            if (!animate)
                actor.opacity = 255;
            else
                Tweener.addTween(actor,
                                 { opacity: 255,
                                   time: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                                   delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                                   transition: 'linear' });
        }

        this._defaultButtonWellActor = actor;
    }

    startSpinning() {
        this.setActorInDefaultButtonWell(this._spinner.actor, true);
    }

    stopSpinning() {
        this.setActorInDefaultButtonWell(null, false);
    }

    clear() {
        this._entry.text = '';
        this.stopSpinning();
        this._authList.clear();
        this._authList.actor.hide();
    }

    setPasswordChar(passwordChar) {
        this._entry.clutter_text.set_password_char(passwordChar);
        this._entry.menu.isPassword = passwordChar != '';
        this._capsLockWarningLabel.visible = passwordChar !== '';
    }

    setQuestion(question) {
        if (this._preemptiveAnswerWatchId) {
            this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
            this._preemptiveAnswerWatchId = 0;
        }

        this._label.set_text(question);

        this._authList.actor.hide();
        this._label.show();
        this._entry.show();

        this._entry.grab_key_focus();
    }

    _fadeInChoiceList() {
       this._authList.actor.opacity = 0;
       this._authList.actor.show();
       this._authList.actor.reactive = false;
       Tweener.addTween(this._authList.actor,
                        { opacity: 255,
                          time: MESSAGE_FADE_OUT_ANIMATION_TIME,
                          transition: 'easeOutQuad',
                          onComplete: () => {
                              this._authList.actor.reactive = true;
                          }
                        });
    }

    setChoiceList(promptMessage, choiceList) {
        this._authList.clear();
        this._authList.label.text = promptMessage;
        for (let key in choiceList) {
            let text = choiceList[key];
            this._authList.addItem(key, text);
        }

        this._label.hide();
        this._entry.hide();
        if (this._message.text == "")
            this._message.hide();
        this._fadeInChoiceList();
    }

    getAnswer() {
        let text;

        if (this._preemptiveAnswer) {
            text = this._preemptiveAnswer;
            this._preemptiveAnswer = null;
        } else {
            text = this._entry.get_text();
        }

        return text;
    }

    _fadeOutMessage() {
        if (this._message.opacity == 0)
            return;
        Tweener.removeTweens(this._message);
        Tweener.addTween(this._message,
                         { opacity: 0,
                           time: MESSAGE_FADE_OUT_ANIMATION_TIME,
                           transition: 'easeOutQuad'
                         });
    }

    setMessage(message, type) {
        if (type == GdmUtil.MessageType.ERROR)
            this._message.add_style_class_name('login-dialog-message-warning');
        else
            this._message.remove_style_class_name('login-dialog-message-warning');

        if (type == GdmUtil.MessageType.HINT)
            this._message.add_style_class_name('login-dialog-message-hint');
        else
            this._message.remove_style_class_name('login-dialog-message-hint');

        this._message.show();
        if (message) {
            Tweener.removeTweens(this._message);
            this._message.text = message;
            this._message.opacity = 255;
        } else {
            this._message.opacity = 0;
        }
    }

    _updateNextButtonSensitivity(sensitive) {
        this.nextButton.reactive = sensitive;
        this.nextButton.can_focus = sensitive;
    }

    updateSensitivity(sensitive) {
        this._updateNextButtonSensitivity(sensitive && !this._authList.actor.visible && (this._entry.text.length > 0 || this.verificationStatus == AuthPromptStatus.VERIFYING));
        this._entry.reactive = sensitive;
        this._entry.clutter_text.editable = sensitive;
    }

    hide() {
        this.setActorInDefaultButtonWell(null, true);
        this.actor.hide();
        this._message.opacity = 0;

        this.setUser(null);

        this.updateSensitivity(true);
        this._entry.set_text('');
    }

    setUser(user) {
        let oldChild = this._userWell.get_child();
        if (oldChild)
            oldChild.destroy();

        if (user) {
            let userWidget = new UserWidget.UserWidget(user);
            this._userWell.set_child(userWidget.actor);
        }
    }

    _onUserStoppedTypePreemptiveAnswer() {
        if (!this._preemptiveAnswerWatchId ||
            this._preemptiveAnswer ||
            this._queryingService)
            return;

        this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
        this._preemptiveAnswerWatchId = 0;

        this._entry.text = '';
        this.updateSensitivity(false);
    }

    reset() {
        let oldStatus = this.verificationStatus;
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
        this.cancelButton.reactive = true;
        this.nextButton.label = _("Next");
        this._preemptiveAnswer = null;

        if (this._preemptiveAnswerWatchId) {
            this._idleMonitor.remove_watch(this._preemptiveAnswerWatchId);
        }
        this._preemptiveAnswerWatchId = this._idleMonitor.add_idle_watch (500,
                                                                          this._onUserStoppedTypePreemptiveAnswer.bind(this));

        if (this._userVerifier)
            this._userVerifier.cancel();

        this._queryingService = null;
        this.clear();
        this._message.opacity = 0;
        this.setUser(null);
        this.stopSpinning();

        if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED)
            this.emit('failed');

        let beginRequestType;

        if (this._mode == AuthPromptMode.UNLOCK_ONLY) {
            // The user is constant at the unlock screen, so it will immediately
            // respond to the request with the username
            beginRequestType = BeginRequestType.PROVIDE_USERNAME;
        } else if (this._userVerifier.serviceIsForeground(GdmUtil.OVIRT_SERVICE_NAME) ||
                   this._userVerifier.serviceIsForeground(GdmUtil.SMARTCARD_SERVICE_NAME)) {
            // We don't need to know the username if the user preempted the login screen
            // with a smartcard or with preauthenticated oVirt credentials
            beginRequestType = BeginRequestType.DONT_PROVIDE_USERNAME;
        } else {
            // In all other cases, we should get the username up front.
            beginRequestType = BeginRequestType.PROVIDE_USERNAME;
        }

        this.emit('reset', beginRequestType);
    }

    addCharacter(unichar) {
        if (!this._entry.visible)
            return;

        this._entry.grab_key_focus();
        this._entry.clutter_text.insert_unichar(unichar);
    }

    begin(params) {
        params = Params.parse(params, { userName: null,
                                        hold: null });

        this.updateSensitivity(false);

        let hold = params.hold;
        if (!hold)
            hold = new Batch.Hold();

        this._userVerifier.begin(params.userName, hold);
        this.verificationStatus = AuthPromptStatus.VERIFYING;
    }

    finish(onComplete) {
        if (!this._userVerifier.hasPendingMessages) {
            this._userVerifier.clear();
            onComplete();
            return;
        }

        let signalId = this._userVerifier.connect('no-more-messages', () => {
            this._userVerifier.disconnect(signalId);
            this._userVerifier.clear();
            onComplete();
        });
    }

    cancel() {
        if (this.verificationStatus == AuthPromptStatus.VERIFICATION_SUCCEEDED) {
            return;
        }
        this.reset();
        this.emit('cancelled');
    }
};
Signals.addSignalMethods(AuthPrompt.prototype);
(uuay)notificationDaemon.jsko// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GdkPixbuf, Gio, GLib, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;

const Config = imports.misc.config;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Params = imports.misc.params;

const { loadInterfaceXML } = imports.misc.fileUtils;

// Should really be defined in Gio.js
const BusIface = loadInterfaceXML('org.freedesktop.DBus');
var BusProxy = Gio.DBusProxy.makeProxyWrapper(BusIface);
function Bus() {
    return new BusProxy(Gio.DBus.session, 'org.freedesktop.DBus', '/org/freedesktop/DBus');
}

const FdoNotificationsIface = loadInterfaceXML('org.freedesktop.Notifications');

var NotificationClosedReason = {
    EXPIRED: 1,
    DISMISSED: 2,
    APP_CLOSED: 3,
    UNDEFINED: 4
};

var Urgency = {
    LOW: 0,
    NORMAL: 1,
    CRITICAL: 2
};

const rewriteRules = {
    'XChat': [
        { pattern:     /^XChat: Private message from: (\S*) \(.*\)$/,
          replacement: '<$1>' },
        { pattern:     /^XChat: New public message from: (\S*) \((.*)\)$/,
          replacement: '$2 <$1>' },
        { pattern:     /^XChat: Highlighted message from: (\S*) \((.*)\)$/,
          replacement: '$2 <$1>' }
    ]
};

var FdoNotificationDaemon = class FdoNotificationDaemon {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications');

        this._sources = [];
        this._senderToPid = {};
        this._notifications = {};
        this._busProxy = new Bus();

        this._nextNotificationId = 1;

        Shell.WindowTracker.get_default().connect('notify::focus-app',
            this._onFocusAppChanged.bind(this));
        Main.overview.connect('hidden',
            this._onFocusAppChanged.bind(this));
    }

    _imageForNotificationData(hints) {
        if (hints['image-data']) {
            let [width, height, rowStride, hasAlpha,
                 bitsPerSample, nChannels, data] = hints['image-data'];
            return Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
                                                      bitsPerSample, width, height, rowStride);
        } else if (hints['image-path']) {
            return this._iconForNotificationData(hints['image-path']);
        }
        return null;
    }

    _fallbackIconForNotificationData(hints) {
        let stockIcon;
        switch (hints.urgency) {
            case Urgency.LOW:
            case Urgency.NORMAL:
                stockIcon = 'dialog-information';
                break;
            case Urgency.CRITICAL:
                stockIcon = 'dialog-error';
                break;
        }
        return new Gio.ThemedIcon({ name: stockIcon });
    }

    _iconForNotificationData(icon) {
        if (icon) {
            if (icon.substr(0, 7) == 'file://')
                return new Gio.FileIcon({ file: Gio.File.new_for_uri(icon) });
            else if (icon[0] == '/')
                return new Gio.FileIcon({ file: Gio.File.new_for_path(icon) });
            else
                return new Gio.ThemedIcon({ name: icon });
        }
        return null;
    }

    _lookupSource(title, pid) {
        for (let i = 0; i < this._sources.length; i++) {
            let source = this._sources[i];
            if (source.pid == pid && source.initialTitle == title)
                return source;
        }
        return null;
    }

    // Returns the source associated with ndata.notification if it is set.
    // If the existing or requested source is associated with a tray icon
    // and passed in pid matches a pid of an existing source, the title
    // match is ignored to enable representing a tray icon and notifications
    // from the same application with a single source.
    //
    // If no existing source is found, a new source is created as long as
    // pid is provided.
    //
    // Either a pid or ndata.notification is needed to retrieve or
    // create a source.
    _getSource(title, pid, ndata, sender) {
        if (!pid && !(ndata && ndata.notification))
            return null;

        // We use notification's source for the notifications we still have
        // around that are getting replaced because we don't keep sources
        // for transient notifications in this._sources, but we still want
        // the notification associated with them to get replaced correctly.
        if (ndata && ndata.notification)
            return ndata.notification.source;

        let source = this._lookupSource(title, pid);
        if (source) {
            source.setTitle(title);
            return source;
        }

        let appId = ndata ? ndata.hints['desktop-entry'] || null : null;
        source = new FdoNotificationDaemonSource(title, pid, sender, appId);

        this._sources.push(source);
        source.connect('destroy', () => {
            let index = this._sources.indexOf(source);
            if (index >= 0)
                this._sources.splice(index, 1);
        });

        Main.messageTray.add(source);
        return source;
    }

    NotifyAsync(params, invocation) {
        let [appName, replacesId, icon, summary, body, actions, hints, timeout] = params;
        let id;

        for (let hint in hints) {
            // unpack the variants
            hints[hint] = hints[hint].deep_unpack();
        }

        hints = Params.parse(hints, { urgency: Urgency.NORMAL }, true);

        // Filter out chat, presence, calls and invitation notifications from
        // Empathy, since we handle that information from telepathyClient.js
        //
        // Note that empathy uses im.received for one to one chats and
        // x-empathy.im.mentioned for multi-user, so we're good here
        if (appName == 'Empathy' && hints['category'] == 'im.received') {
            // Ignore replacesId since we already sent back a
            // NotificationClosed for that id.
            id = this._nextNotificationId++;
            let idle_id = Mainloop.idle_add(() => {
                this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(idle_id, '[gnome-shell] this._emitNotificationClosed');
            return invocation.return_value(GLib.Variant.new('(u)', [id]));
        }

        let rewrites = rewriteRules[appName];
        if (rewrites) {
            for (let i = 0; i < rewrites.length; i++) {
                let rule = rewrites[i];
                if (summary.search(rule.pattern) != -1)
                    summary = summary.replace(rule.pattern, rule.replacement);
            }
        }

        // Be compatible with the various hints for image data and image path
        // 'image-data' and 'image-path' are the latest name of these hints, introduced in 1.2

        if (!hints['image-path'] && hints['image_path'])
            hints['image-path'] = hints['image_path']; // version 1.1 of the spec

        if (!hints['image-data']) {
            if (hints['image_data'])
                hints['image-data'] = hints['image_data']; // version 1.1 of the spec
            else if (hints['icon_data'] && !hints['image-path'])
                // early versions of the spec; 'icon_data' should only be used if 'image-path' is not available
                hints['image-data'] = hints['icon_data'];
        }

        let ndata = { appName: appName,
                      icon: icon,
                      summary: summary,
                      body: body,
                      actions: actions,
                      hints: hints,
                      timeout: timeout };
        if (replacesId != 0 && this._notifications[replacesId]) {
            ndata.id = id = replacesId;
            ndata.notification = this._notifications[replacesId].notification;
        } else {
            replacesId = 0;
            ndata.id = id = this._nextNotificationId++;
        }
        this._notifications[id] = ndata;

        let sender = invocation.get_sender();
        let pid = this._senderToPid[sender];

        let source = this._getSource(appName, pid, ndata, sender, null);

        if (source) {
            this._notifyForSource(source, ndata);
            return invocation.return_value(GLib.Variant.new('(u)', [id]));
        }

        if (replacesId) {
            // There's already a pending call to GetConnectionUnixProcessID,
            // which will see the new notification data when it finishes,
            // so we don't have to do anything.
            return invocation.return_value(GLib.Variant.new('(u)', [id]));;
        }

        this._busProxy.GetConnectionUnixProcessIDRemote(sender, (result, excp) => {
            // The app may have updated or removed the notification
            ndata = this._notifications[id];
            if (!ndata)
                return;

            if (excp) {
                logError(excp, 'Call to GetConnectionUnixProcessID failed');
                return;
            }

            let [pid] = result;
            source = this._getSource(appName, pid, ndata, sender, null);

            this._senderToPid[sender] = pid;
            source.connect('destroy', () => {
                delete this._senderToPid[sender];
            });
            this._notifyForSource(source, ndata);
        });

        return invocation.return_value(GLib.Variant.new('(u)', [id]));
    }

    _notifyForSource(source, ndata) {
        let [id, icon, summary, body, actions, hints, notification] =
            [ndata.id, ndata.icon, ndata.summary, ndata.body,
             ndata.actions, ndata.hints, ndata.notification];

        if (notification == null) {
            notification = new MessageTray.Notification(source);
            ndata.notification = notification;
            notification.connect('destroy', (n, reason) => {
                delete this._notifications[ndata.id];
                let notificationClosedReason;
                switch (reason) {
                    case MessageTray.NotificationDestroyedReason.EXPIRED:
                        notificationClosedReason = NotificationClosedReason.EXPIRED;
                        break;
                    case MessageTray.NotificationDestroyedReason.DISMISSED:
                        notificationClosedReason = NotificationClosedReason.DISMISSED;
                        break;
                    case MessageTray.NotificationDestroyedReason.SOURCE_CLOSED:
                        notificationClosedReason = NotificationClosedReason.APP_CLOSED;
                        break;
                }
                this._emitNotificationClosed(ndata.id, notificationClosedReason);
            });
        }

        let gicon = this._iconForNotificationData(icon);
        let gimage = this._imageForNotificationData(hints);

        // If an icon is not specified, we use 'image-data' or 'image-path' hint for an icon
        // and don't show a large image. There are currently many applications that use
        // notify_notification_set_icon_from_pixbuf() from libnotify, which in turn sets
        // the 'image-data' hint. These applications don't typically pass in 'app_icon'
        // argument to Notify() and actually expect the pixbuf to be shown as an icon.
        // So the logic here does the right thing for this case. If both an icon and either
        // one of 'image-data' or 'image-path' are specified, the icon and takes precedence.
        if (!gicon && gimage)
            gicon = gimage;
        else if (!gicon)
            gicon = this._fallbackIconForNotificationData(hints);

        notification.update(summary, body, { gicon: gicon,
                                             bannerMarkup: true,
                                             clear: true,
                                             soundFile: hints['sound-file'],
                                             soundName: hints['sound-name'] });

        let hasDefaultAction = false;

        if (actions.length) {
            for (let i = 0; i < actions.length - 1; i += 2) {
                let [actionId, label] = [actions[i], actions[i+1]];
                if (actionId == 'default')
                    hasDefaultAction = true;
                else
                    notification.addAction(label, () => {
                        this._emitActionInvoked(ndata.id, actionId);
                    });
            }
        }

        if (hasDefaultAction) {
            notification.connect('activated', () => {
                this._emitActionInvoked(ndata.id, 'default');
            });
        } else {
            notification.connect('activated', () => {
                source.open();
            });
        }

        switch (hints.urgency) {
            case Urgency.LOW:
                notification.setUrgency(MessageTray.Urgency.LOW);
                break;
            case Urgency.NORMAL:
                notification.setUrgency(MessageTray.Urgency.NORMAL);
                break;
            case Urgency.CRITICAL:
                notification.setUrgency(MessageTray.Urgency.CRITICAL);
                break;
        }
        notification.setResident(!!hints.resident);
        // 'transient' is a reserved keyword in JS, so we have to retrieve the value
        // of the 'transient' hint with hints['transient'] rather than hints.transient
        notification.setTransient(!!hints['transient']);

        let privacyScope = (hints['x-gnome-privacy-scope'] || 'user');
        notification.setPrivacyScope(privacyScope == 'system' ? MessageTray.PrivacyScope.SYSTEM
                                                              : MessageTray.PrivacyScope.USER);

        let sourceGIcon = source.useNotificationIcon ? gicon : null;
        source.processNotification(notification, sourceGIcon);
    }

    CloseNotification(id) {
        let ndata = this._notifications[id];
        if (ndata) {
            if (ndata.notification)
                ndata.notification.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
            delete this._notifications[id];
        }
    }

    GetCapabilities() {
        return [
            'actions',
            // 'action-icons',
            'body',
            // 'body-hyperlinks',
            // 'body-images',
            'body-markup',
            // 'icon-multi',
            'icon-static',
            'persistence',
            'sound',
        ];
    }

    GetServerInformation() {
        return [
            Config.PACKAGE_NAME,
            'GNOME',
            Config.PACKAGE_VERSION,
            '1.2'
        ];
    }

    _onFocusAppChanged() {
        let tracker = Shell.WindowTracker.get_default();
        if (!tracker.focus_app)
            return;

        for (let i = 0; i < this._sources.length; i++) {
            let source = this._sources[i];
            if (source.app == tracker.focus_app) {
                source.destroyNonResidentNotifications();
                return;
            }
        }
    }

    _emitNotificationClosed(id, reason) {
        this._dbusImpl.emit_signal('NotificationClosed',
                                   GLib.Variant.new('(uu)', [id, reason]));
    }

    _emitActionInvoked(id, action) {
        this._dbusImpl.emit_signal('ActionInvoked',
                                   GLib.Variant.new('(us)', [id, action]));
    }
};

var FdoNotificationDaemonSource =
class FdoNotificationDaemonSource extends MessageTray.Source {
    constructor(title, pid, sender, appId) {
        super(title);

        this.pid = pid;
        this.app = this._getApp(appId);

        this.initialTitle = title;

        if (this.app)
            this.title = this.app.get_name();
        else
            this.useNotificationIcon = true;

        if (sender)
            this._nameWatcherId = Gio.DBus.session.watch_name(sender,
                                                              Gio.BusNameWatcherFlags.NONE,
                                                              null,
                                                              this._onNameVanished.bind(this));
        else
            this._nameWatcherId = 0;
    }

    _createPolicy() {
        if (this.app && this.app.get_app_info()) {
            let id = this.app.get_id().replace(/\.desktop$/,'');
            return new MessageTray.NotificationApplicationPolicy(id);
        } else {
            return new MessageTray.NotificationGenericPolicy();
        }
    }

    _onNameVanished() {
        // Destroy the notification source when its sender is removed from DBus.
        // Only do so if this.app is set to avoid removing "notify-send" sources, senders
        // of which аre removed from DBus immediately.
        // Sender being removed from DBus would normally result in a tray icon being removed,
        // so allow the code path that handles the tray icon being removed to handle that case.
        if (this.app)
            this.destroy();
    }

    processNotification(notification, gicon) {
        if (gicon)
            this._gicon = gicon;
        this.iconUpdated();

        let tracker = Shell.WindowTracker.get_default();
        if (notification.resident && this.app && tracker.focus_app == this.app)
            this.pushNotification(notification);
        else
            this.notify(notification);
    }

    _getApp(appId) {
        let app;

        app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
        if (app != null)
            return app;

        if (appId) {
            app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop');
            if (app != null)
                return app;
        }

        return null;
    }

    setTitle(title) {
        // Do nothing if .app is set, we don't want to override the
        // app name with whatever is provided through libnotify (usually
        // garbage)
        if (this.app)
            return;

        super.setTitle(title);
    }

    open() {
        this.openApp();
        this.destroyNonResidentNotifications();
    }

    openApp() {
        if (this.app == null)
            return;

        this.app.activate();
        Main.overview.hide();
        Main.panel.closeCalendar();
    }

    destroy() {
        if (this._nameWatcherId) {
            Gio.DBus.session.unwatch_name(this._nameWatcherId);
            this._nameWatcherId = 0;
        }

        super.destroy();
    }

    createIcon(size) {
        if (this.app) {
            return this.app.create_icon_texture(size);
        } else if (this._gicon) {
            return new St.Icon({ gicon: this._gicon,
                                 icon_size: size });
        } else {
            return null;
        }
    }
};

const PRIORITY_URGENCY_MAP = {
    low: MessageTray.Urgency.LOW,
    normal: MessageTray.Urgency.NORMAL,
    high: MessageTray.Urgency.HIGH,
    urgent: MessageTray.Urgency.CRITICAL
};

var GtkNotificationDaemonNotification =
class GtkNotificationDaemonNotification extends MessageTray.Notification {
    constructor(source, notification) {
        super(source);
        this._serialized = GLib.Variant.new('a{sv}', notification);

        let { "title": title,
              "body": body,
              "icon": gicon,
              "urgent": urgent,
              "priority": priority,
              "buttons": buttons,
              "default-action": defaultAction,
              "default-action-target": defaultActionTarget,
              "timestamp": time } = notification;

        if (priority) {
            let urgency = PRIORITY_URGENCY_MAP[priority.unpack()];
            this.setUrgency(urgency != undefined ? urgency : MessageTray.Urgency.NORMAL);
        } else if (urgent) {
            this.setUrgency(urgent.unpack() ? MessageTray.Urgency.CRITICAL
                            : MessageTray.Urgency.NORMAL);
        } else {
            this.setUrgency(MessageTray.Urgency.NORMAL);
        }

        if (buttons) {
            buttons.deep_unpack().forEach(button => {
                this.addAction(button.label.unpack(), () => {
                    this._onButtonClicked(button);
                });
            });
        }

        this._defaultAction = defaultAction ? defaultAction.unpack() : null;
        this._defaultActionTarget = defaultActionTarget;

        this.update(title.unpack(), body ? body.unpack() : null,
                    { gicon: gicon ? Gio.icon_deserialize(gicon) : null,
                      datetime : time ? GLib.DateTime.new_from_unix_local(time.unpack()) : null });
    }

    _activateAction(namespacedActionId, target) {
        if (namespacedActionId) {
            if (namespacedActionId.startsWith('app.')) {
                let actionId = namespacedActionId.slice('app.'.length);
                this.source.activateAction(actionId, target);
            }
        } else {
            this.source.open();
        }
    }

    _onButtonClicked(button) {
        let { 'action': action, 'target': actionTarget } = button;
        this._activateAction(action.unpack(), actionTarget);
    }

    activate() {
        this._activateAction(this._defaultAction, this._defaultActionTarget);
        super.activate();
    }

    serialize() {
        return this._serialized;
    }
};

const FdoApplicationIface = loadInterfaceXML('org.freedesktop.Application');
const FdoApplicationProxy = Gio.DBusProxy.makeProxyWrapper(FdoApplicationIface);

function objectPathFromAppId(appId) {
    return '/' + appId.replace(/\./g, '/').replace(/-/g, '_');
}

function getPlatformData() {
    let startupId = GLib.Variant.new('s', '_TIME' + global.get_current_time());
    return { "desktop-startup-id": startupId };
}

function InvalidAppError() {}

var GtkNotificationDaemonAppSource = 
class GtkNotificationDaemonAppSource extends MessageTray.Source {
    constructor(appId) {
        let objectPath = objectPathFromAppId(appId);
        if (!GLib.Variant.is_object_path(objectPath))
            throw new InvalidAppError();

        let app = Shell.AppSystem.get_default().lookup_app(appId + '.desktop');
        if (!app)
            throw new InvalidAppError();

        super(app.get_name());

        this._appId = appId;
        this._app = app;
        this._objectPath = objectPath;

        this._notifications = {};
        this._notificationPending = false;
    }

    createIcon(size) {
        return this._app.create_icon_texture(size);
    }

    _createPolicy() {
        return new MessageTray.NotificationApplicationPolicy(this._appId);
    }

    _createApp(callback) {
        return new FdoApplicationProxy(Gio.DBus.session, this._appId, this._objectPath, callback);
    }

    _createNotification(params) {
        return new GtkNotificationDaemonNotification(this, params);
    }

    activateAction(actionId, target) {
        this._createApp((app, error) => {
            if (error == null)
                app.ActivateActionRemote(actionId, target ? [target] : [], getPlatformData());
            else
                logError(error, 'Failed to activate application proxy');
        });
        Main.overview.hide();
        Main.panel.closeCalendar();
    }

    open() {
        this._createApp((app, error) => {
            if (error == null)
                app.ActivateRemote(getPlatformData());
            else
                logError(error, 'Failed to open application proxy');
        });
        Main.overview.hide();
        Main.panel.closeCalendar();
    }

    addNotification(notificationId, notificationParams, showBanner) {
        this._notificationPending = true;

        if (this._notifications[notificationId])
            this._notifications[notificationId].destroy(MessageTray.NotificationDestroyedReason.REPLACED);

        let notification = this._createNotification(notificationParams);
        notification.connect('destroy', () => {
            delete this._notifications[notificationId];
        });
        this._notifications[notificationId] = notification;

        if (showBanner)
            this.notify(notification);
        else
            this.pushNotification(notification);

        this._notificationPending = false;
    }

    destroy(reason) {
        if (this._notificationPending)
            return;
        super.destroy(reason);
    }

    removeNotification(notificationId) {
        if (this._notifications[notificationId])
            this._notifications[notificationId].destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
    }

    serialize() {
        let notifications = [];
        for (let notificationId in this._notifications) {
            let notification = this._notifications[notificationId];
            notifications.push([notificationId, notification.serialize()]);
        }
        return [this._appId, notifications];
    }
};

const GtkNotificationsIface = loadInterfaceXML('org.gtk.Notifications');

var GtkNotificationDaemon = class GtkNotificationDaemon {
    constructor() {
        this._sources = {};

        this._loadNotifications();

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GtkNotificationsIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gtk/Notifications');

        Gio.DBus.session.own_name('org.gtk.Notifications', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    _ensureAppSource(appId) {
        if (this._sources[appId])
            return this._sources[appId];

        let source = new GtkNotificationDaemonAppSource(appId);

        source.connect('destroy', () => {
            delete this._sources[appId];
            this._saveNotifications();
        });
        source.connect('count-updated', this._saveNotifications.bind(this));
        Main.messageTray.add(source);
        this._sources[appId] = source;
        return source;
    }

    _loadNotifications() {
        this._isLoading = true;

        try {
            let value = global.get_persistent_state('a(sa(sv))', 'notifications');
            if (value) {
                let sources = value.deep_unpack();
                sources.forEach(([appId, notifications]) => {
                    if (notifications.length == 0)
                        return;

                    let source;
                    try {
                        source = this._ensureAppSource(appId);
                    } catch (e) {
                        if (e instanceof InvalidAppError)
                            return;
                        throw e;
                    }

                    notifications.forEach(([notificationId, notification]) => {
                        source.addNotification(notificationId, notification.deep_unpack(), false);
                    });
                });
            }
        } catch (e) {
            logError(e, 'Failed to load saved notifications');
        } finally {
            this._isLoading = false;
        }
    }

    _saveNotifications() {
        if (this._isLoading)
            return;

        let sources = [];
        for (let appId in this._sources) {
            let source = this._sources[appId];
            sources.push(source.serialize());
        }

        global.set_persistent_state('notifications', new GLib.Variant('a(sa(sv))', sources));
    }

    AddNotificationAsync(params, invocation) {
        let [appId, notificationId, notification] = params;

        let source;
        try {
            source = this._ensureAppSource(appId);
        } catch(e) {
            if (e instanceof InvalidAppError) {
                invocation.return_dbus_error('org.gtk.Notifications.InvalidApp', 'The app by ID "%s" could not be found'.format(appId));
                return;
            }
            throw e;
        }

        let timestamp = GLib.DateTime.new_now_local().to_unix();
        notification['timestamp'] = new GLib.Variant('x', timestamp);

        source.addNotification(notificationId, notification, true);

        invocation.return_value(null);
    }

    RemoveNotificationAsync(params, invocation) {
        let [appId, notificationId] = params;
        let source = this._sources[appId];
        if (source)
            source.removeNotification(notificationId);

        invocation.return_value(null);
    }
};

var NotificationDaemon = class NotificationDaemon {
    constructor() {
        this._fdoNotificationDaemon = new FdoNotificationDaemon();
        this._gtkNotificationDaemon = new GtkNotificationDaemon();
    }
};
(uuay)polkitAgent.js>// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { AccountsService, Clutter, Gio, GLib,
        Pango, PolkitAgent, Polkit, Shell, St } = imports.gi;
const Signals = imports.signals;

const Animation = imports.ui.animation;
const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const UserWidget = imports.ui.userWidget;

var DIALOG_ICON_SIZE = 48;

var WORK_SPINNER_ICON_SIZE = 16;

var AuthenticationDialog = class extends ModalDialog.ModalDialog {
    constructor(actionId, body, cookie, userNames) {
        super({ styleClass: 'prompt-dialog' });

        this.actionId = actionId;
        this.message = body;
        this.userNames = userNames;
        this._wasDismissed = false;

        this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
            this._group.visible = !Main.sessionMode.isLocked;
        });

        this.connect('closed', this._onDialogClosed.bind(this));

        let icon = new Gio.ThemedIcon({ name: 'dialog-password-symbolic' });
        let title = _("Authentication Required");

        let content = new Dialog.MessageDialogContent({ icon, title, body });
        this.contentLayout.add_actor(content);

        if (userNames.length > 1) {
            log('polkitAuthenticationAgent: Received ' + userNames.length +
                ' identities that can be used for authentication. Only ' +
                'considering one.');
        }

        let userName = GLib.get_user_name();
        if (userNames.indexOf(userName) < 0)
            userName = 'root';
        if (userNames.indexOf(userName) < 0)
            userName = userNames[0];

        this._user = AccountsService.UserManager.get_default().get_user(userName);
        let userRealName = this._user.get_real_name()
        this._userLoadedId = this._user.connect('notify::is_loaded',
                                                this._onUserChanged.bind(this));
        this._userChangedId = this._user.connect('changed',
                                                 this._onUserChanged.bind(this));

        // Special case 'root'
        let userIsRoot = false;
        if (userName == 'root') {
            userIsRoot = true;
            userRealName = _("Administrator");
        }

        if (userIsRoot) {
            let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-root-label',
                                            text: userRealName }));
            content.messageBox.add(userLabel, { x_fill: false,
                                                x_align: St.Align.START });
        } else {
            let userBox = new St.BoxLayout({ style_class: 'polkit-dialog-user-layout',
                                             vertical: false });
            content.messageBox.add(userBox);
            this._userAvatar = new UserWidget.Avatar(this._user,
                                                     { iconSize: DIALOG_ICON_SIZE,
                                                       styleClass: 'polkit-dialog-user-icon' });
            this._userAvatar.actor.hide();
            userBox.add(this._userAvatar.actor,
                        { x_fill:  true,
                          y_fill:  false,
                          x_align: St.Align.END,
                          y_align: St.Align.START });
            let userLabel = new St.Label(({ style_class: 'polkit-dialog-user-label',
                                            text: userRealName }));
            userBox.add(userLabel,
                        { x_fill:  true,
                          y_fill:  false,
                          x_align: St.Align.END,
                          y_align: St.Align.MIDDLE });
        }

        this._onUserChanged();

        this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' });
        content.messageBox.add(this._passwordBox);
        this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label' }));
        this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE });
        this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
                                             text: "",
                                             can_focus: true});
        ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
        this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
        this._passwordBox.add(this._passwordEntry,
                              { expand: true });

        this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, true);
        this._passwordBox.add(this._workSpinner.actor);

        this.setInitialKeyFocus(this._passwordEntry);
        this._passwordBox.hide();
        this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({ style_class: 'prompt-dialog-caps-lock-warning' });
        content.messageBox.add(this._capsLockWarningLabel);

        this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label' });
        this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._errorMessageLabel.clutter_text.line_wrap = true;
        content.messageBox.add(this._errorMessageLabel, { x_fill: false, x_align: St.Align.START });
        this._errorMessageLabel.hide();

        this._infoMessageLabel = new St.Label({ style_class: 'prompt-dialog-info-label' });
        this._infoMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._infoMessageLabel.clutter_text.line_wrap = true;
        content.messageBox.add(this._infoMessageLabel);
        this._infoMessageLabel.hide();

        /* text is intentionally non-blank otherwise the height is not the same as for
         * infoMessage and errorMessageLabel - but it is still invisible because
         * gnome-shell.css sets the color to be transparent
         */
        this._nullMessageLabel = new St.Label({ style_class: 'prompt-dialog-null-label',
                                                text: 'abc'});
        this._nullMessageLabel.add_style_class_name('hidden');
        this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._nullMessageLabel.clutter_text.line_wrap = true;
        content.messageBox.add(this._nullMessageLabel);
        this._nullMessageLabel.show();

        this._cancelButton = this.addButton({ label: _("Cancel"),
                                              action: this.cancel.bind(this),
                                              key: Clutter.Escape });
        this._okButton = this.addButton({ label:  _("Authenticate"),
                                          action: this._onAuthenticateButtonPressed.bind(this),
                                          default: true });

        this._doneEmitted = false;

        this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
        this._cookie = cookie;
    }

    _setWorking(working) {
        if (working)
            this._workSpinner.play();
        else
            this._workSpinner.stop();
    }

    performAuthentication() {
        this._destroySession();
        this._session = new PolkitAgent.Session({ identity: this._identityToAuth,
                                                  cookie: this._cookie });
        this._sessionCompletedId = this._session.connect('completed', this._onSessionCompleted.bind(this));
        this._sessionRequestId = this._session.connect('request', this._onSessionRequest.bind(this));
        this._sessionShowErrorId = this._session.connect('show-error', this._onSessionShowError.bind(this));
        this._sessionShowInfoId = this._session.connect('show-info', this._onSessionShowInfo.bind(this));
        this._session.initiate();
    }

    _ensureOpen() {
        // NOTE: ModalDialog.open() is safe to call if the dialog is
        // already open - it just returns true without side-effects
        if (!this.open(global.get_current_time())) {
            // This can fail if e.g. unable to get input grab
            //
            // In an ideal world this wouldn't happen (because the
            // Shell is in complete control of the session) but that's
            // just not how things work right now.
            //
            // One way to make this happen is by running 'sleep 3;
            // pkexec bash' and then opening a popup menu.
            //
            // We could add retrying if this turns out to be a problem

            log('polkitAuthenticationAgent: Failed to show modal dialog.' +
                ' Dismissing authentication request for action-id ' + this.actionId +
                ' cookie ' + this._cookie);
            this._emitDone(true);
        }
    }

    _emitDone(dismissed) {
        if (!this._doneEmitted) {
            this._doneEmitted = true;
            this.emit('done', dismissed);
        }
    }

    _updateSensitivity(sensitive) {
        this._passwordEntry.reactive = sensitive;
        this._passwordEntry.clutter_text.editable = sensitive;

        this._okButton.can_focus = sensitive;
        this._okButton.reactive = sensitive;
        this._setWorking(!sensitive);
    }

    _onEntryActivate() {
        let response = this._passwordEntry.get_text();
        this._updateSensitivity(false);
        this._session.response(response);
        // When the user responds, dismiss already shown info and
        // error texts (if any)
        this._errorMessageLabel.hide();
        this._infoMessageLabel.hide();
        this._nullMessageLabel.show();
    }

    _onAuthenticateButtonPressed() {
        this._onEntryActivate();
    }

    _onSessionCompleted(session, gainedAuthorization) {
        if (this._completed || this._doneEmitted)
            return;

        this._completed = true;

        /* Yay, all done */
        if (gainedAuthorization) {
            this._emitDone(false);

        } else {
            /* Unless we are showing an existing error message from the PAM
             * module (the PAM module could be reporting the authentication
             * error providing authentication-method specific information),
             * show "Sorry, that didn't work. Please try again."
             */
            if (!this._errorMessageLabel.visible && !this._wasDismissed) {
                /* Translators: "that didn't work" refers to the fact that the
                 * requested authentication was not gained; this can happen
                 * because of an authentication error (like invalid password),
                 * for instance. */
                this._errorMessageLabel.set_text(_("Sorry, that didn’t work. Please try again."));
                this._errorMessageLabel.show();
                this._infoMessageLabel.hide();
                this._nullMessageLabel.hide();
            }

            /* Try and authenticate again */
            this.performAuthentication();
        }
    }

    _onSessionRequest(session, request, echo_on) {
        // Cheap localization trick
        if (request == 'Password:' || request == 'Password: ')
            this._passwordLabel.set_text(_("Password:"));
        else
            this._passwordLabel.set_text(request);

        if (echo_on)
            this._passwordEntry.clutter_text.set_password_char('');
        else
            this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE

        this._passwordBox.show();
        this._passwordEntry.set_text('');
        this._passwordEntry.grab_key_focus();
        this._updateSensitivity(true);
        this._ensureOpen();
    }

    _onSessionShowError(session, text) {
        this._passwordEntry.set_text('');
        this._errorMessageLabel.set_text(text);
        this._errorMessageLabel.show();
        this._infoMessageLabel.hide();
        this._nullMessageLabel.hide();
        this._ensureOpen();
    }

    _onSessionShowInfo(session, text) {
        this._passwordEntry.set_text('');
        this._infoMessageLabel.set_text(text);
        this._infoMessageLabel.show();
        this._errorMessageLabel.hide();
        this._nullMessageLabel.hide();
        this._ensureOpen();
    }

    _destroySession() {
        if (this._session) {
            if (!this._completed)
                this._session.cancel();
            this._completed = false;

            this._session.disconnect(this._sessionCompletedId);
            this._session.disconnect(this._sessionRequestId);
            this._session.disconnect(this._sessionShowErrorId);
            this._session.disconnect(this._sessionShowInfoId);
            this._session = null;
        }
    }

    _onUserChanged() {
        if (this._user.is_loaded && this._userAvatar) {
            this._userAvatar.update();
            this._userAvatar.actor.show();
        }
    }

    cancel() {
        this._wasDismissed = true;
        this.close(global.get_current_time());
        this._emitDone(true);
    }

    _onDialogClosed() {
        if (this._sessionUpdatedId)
            Main.sessionMode.disconnect(this._sessionUpdatedId);
        this._sessionUpdatedId = 0;

        if (this._user) {
            this._user.disconnect(this._userLoadedId);
            this._user.disconnect(this._userChangedId);
            this._user = null;
        }

        this._destroySession();
    }
};
Signals.addSignalMethods(AuthenticationDialog.prototype);

var AuthenticationAgent = class {
    constructor() {
        this._currentDialog = null;
        this._handle = null;
        this._native = new Shell.PolkitAuthenticationAgent();
        this._native.connect('initiate', this._onInitiate.bind(this));
        this._native.connect('cancel', this._onCancel.bind(this));
        this._sessionUpdatedId = 0;
    }

    enable() {
        try {
            this._native.register();
        } catch(e) {
            log('Failed to register AuthenticationAgent');
        }
    }

    disable() {
        try {
            this._native.unregister();
        } catch(e) {
            log('Failed to unregister AuthenticationAgent');
        }
    }

    _onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames) {
        // Don't pop up a dialog while locked
        if (Main.sessionMode.isLocked) {
            this._sessionUpdatedId = Main.sessionMode.connect('updated', () => {
                Main.sessionMode.disconnect(this._sessionUpdatedId);
                this._sessionUpdatedId = 0;

                this._onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames);
            });
            return;
        }

        this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);

        // We actually don't want to open the dialog until we know for
        // sure that we're going to interact with the user. For
        // example, if the password for the identity to auth is blank
        // (which it will be on a live CD) then there will be no
        // conversation at all... of course, we don't *know* that
        // until we actually try it.
        //
        // See https://bugzilla.gnome.org/show_bug.cgi?id=643062 for more
        // discussion.

        this._currentDialog.connect('done', this._onDialogDone.bind(this));
        this._currentDialog.performAuthentication();
    }

    _onCancel(nativeAgent) {
        this._completeRequest(false);
    }

    _onDialogDone(dialog, dismissed) {
        this._completeRequest(dismissed);
    }

    _completeRequest(dismissed) {
        this._currentDialog.close();
        this._currentDialog = null;

        if (this._sessionUpdatedId)
            Main.sessionMode.disconnect(this._sessionUpdatedId);
        this._sessionUpdatedId = 0;

        this._native.complete(dismissed);
    }
};

var Component = AuthenticationAgent;
(uuay)shellDBus.js_2// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib, Meta, Shell } = imports.gi;

const Config = imports.misc.config;
const ExtensionDownloader = imports.ui.extensionDownloader;
const ExtensionUtils = imports.misc.extensionUtils;
const Main = imports.ui.main;
const Screenshot = imports.ui.screenshot;

const { loadInterfaceXML } = imports.misc.fileUtils;

const GnomeShellIface = loadInterfaceXML('org.gnome.Shell');
const ScreenSaverIface = loadInterfaceXML('org.gnome.ScreenSaver');

var GnomeShell = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');

        this._extensionsService = new GnomeShellExtensions();
        this._screenshotService = new Screenshot.ScreenshotService();

        this._grabbedAccelerators = new Map();
        this._grabbers = new Map();

        global.display.connect('accelerator-activated',
            (display, action, deviceid, timestamp) => {
                this._emitAcceleratorActivated(action, deviceid, timestamp);
            });

        this._cachedOverviewVisible = false;
        Main.overview.connect('showing',
                              this._checkOverviewVisibleChanged.bind(this));
        Main.overview.connect('hidden',
                              this._checkOverviewVisibleChanged.bind(this));
    }

    /**
     * Eval:
     * @code: A string containing JavaScript code
     *
     * This function executes arbitrary code in the main
     * loop, and returns a boolean success and
     * JSON representation of the object as a string.
     *
     * If evaluation completes without throwing an exception,
     * then the return value will be [true, JSON.stringify(result)].
     * If evaluation fails, then the return value will be
     * [false, JSON.stringify(exception)];
     *
     */
    Eval(code) {
        if (!global.settings.get_boolean('development-tools'))
            return [false, ''];

        let returnValue;
        let success;
        try {
            returnValue = JSON.stringify(eval(code));
            // A hack; DBus doesn't have null/undefined
            if (returnValue == undefined)
                returnValue = '';
            success = true;
        } catch (e) {
            returnValue = '' + e;
            success = false;
        }
        return [success, returnValue];
    }

    FocusSearch() {
        Main.overview.focusSearch();
    }

    ShowOSD(params) {
        for (let param in params)
            params[param] = params[param].deep_unpack();

        let { connector,
              label,
              level,
              max_level: maxLevel,
              icon: serializedIcon } = params;

        let monitorIndex = -1;
        if (connector) {
            let monitorManager = Meta.MonitorManager.get();
            monitorIndex = monitorManager.get_monitor_for_connector(connector);
        }

        let icon = null;
        if (serializedIcon)
            icon = Gio.Icon.new_for_string(serializedIcon);

        Main.osdWindowManager.show(monitorIndex, icon, label, level, maxLevel);
    }

    FocusApp(id) {
        this.ShowApplications();
        Main.overview.viewSelector.appDisplay.selectApp(id);
    }

    ShowApplications() {
        Main.overview.viewSelector.showApps();
    }

    GrabAcceleratorAsync(params, invocation) {
        let [accel, modeFlags, grabFlags] = params;
        let sender = invocation.get_sender();
        let bindingAction = this._grabAcceleratorForSender(accel, modeFlags, grabFlags, sender);
        return invocation.return_value(GLib.Variant.new('(u)', [bindingAction]));
    }

    GrabAcceleratorsAsync(params, invocation) {
        let [accels] = params;
        let sender = invocation.get_sender();
        let bindingActions = [];
        for (let i = 0; i < accels.length; i++) {
            let [accel, modeFlags, grabFlags] = accels[i];
            bindingActions.push(this._grabAcceleratorForSender(accel, modeFlags, grabFlags, sender));
        }
        return invocation.return_value(GLib.Variant.new('(au)', [bindingActions]));
    }

    UngrabAcceleratorAsync(params, invocation) {
        let [action] = params;
        let sender = invocation.get_sender();
        let ungrabSucceeded = this._ungrabAcceleratorForSender(action, sender);

        return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
    }

    UngrabAcceleratorsAsync(params, invocation) {
        let [actions] = params;
        let sender = invocation.get_sender();
        let ungrabSucceeded = true;

        for (let i = 0; i < actions.length; i++)
            ungrabSucceeded &= this._ungrabAcceleratorForSender(actions[i], sender);

        return invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
    }

    _emitAcceleratorActivated(action, deviceid, timestamp) {
        let destination = this._grabbedAccelerators.get(action);
        if (!destination)
            return;

        let connection = this._dbusImpl.get_connection();
        let info = this._dbusImpl.get_info();
        let params = { 'device-id': GLib.Variant.new('u', deviceid),
                       'timestamp': GLib.Variant.new('u', timestamp),
                       'action-mode': GLib.Variant.new('u', Main.actionMode) };
        connection.emit_signal(destination,
                               this._dbusImpl.get_object_path(),
                               info ? info.name : null,
                               'AcceleratorActivated',
                               GLib.Variant.new('(ua{sv})', [action, params]));
    }

    _grabAcceleratorForSender(accelerator, modeFlags, grabFlags, sender) {
        let bindingAction = global.display.grab_accelerator(accelerator, grabFlags);
        if (bindingAction == Meta.KeyBindingAction.NONE)
            return Meta.KeyBindingAction.NONE;

        let bindingName = Meta.external_binding_name_for_action(bindingAction);
        Main.wm.allowKeybinding(bindingName, modeFlags);

        this._grabbedAccelerators.set(bindingAction, sender);

        if (!this._grabbers.has(sender)) {
            let id = Gio.bus_watch_name(Gio.BusType.SESSION, sender, 0, null,
                                        this._onGrabberBusNameVanished.bind(this));
            this._grabbers.set(sender, id);
        }

        return bindingAction;
    }

    _ungrabAccelerator(action) {
        let ungrabSucceeded = global.display.ungrab_accelerator(action);
        if (ungrabSucceeded)
            this._grabbedAccelerators.delete(action);

        return ungrabSucceeded;
    }

    _ungrabAcceleratorForSender(action, sender) {
        let grabbedBy = this._grabbedAccelerators.get(action);
        if (sender != grabbedBy)
            return false;

        return this._ungrabAccelerator(action);
    }

    _onGrabberBusNameVanished(connection, name) {
        let grabs = this._grabbedAccelerators.entries();
        for (let [action, sender] of grabs) {
            if (sender == name)
                this._ungrabAccelerator(action);
        }
        Gio.bus_unwatch_name(this._grabbers.get(name));
        this._grabbers.delete(name);
    }

    ShowMonitorLabels2Async(params, invocation) {
        let sender = invocation.get_sender();
        let [dict] = params;
        Main.osdMonitorLabeler.show(sender, dict);
    }

    HideMonitorLabelsAsync(params, invocation) {
        let sender = invocation.get_sender();
        Main.osdMonitorLabeler.hide(sender);
    }

    _checkOverviewVisibleChanged() {
        if (Main.overview.visible !== this._cachedOverviewVisible) {
            this._cachedOverviewVisible = Main.overview.visible;
            this._dbusImpl.emit_property_changed('OverviewActive', new GLib.Variant('b', this._cachedOverviewVisible));
        }
    }

    get Mode() {
        return global.session_mode;
    }

    get OverviewActive() {
        return this._cachedOverviewVisible;
    }

    set OverviewActive(visible) {
        if (visible)
            Main.overview.show();
        else
            Main.overview.hide();
    }

    get ShellVersion() {
        return Config.PACKAGE_VERSION;
    }
};

const GnomeShellExtensionsIface = loadInterfaceXML('org.gnome.Shell.Extensions');

var GnomeShellExtensions = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellExtensionsIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/Shell');
        Main.extensionManager.connect('extension-state-changed',
                                      this._extensionStateChanged.bind(this));
    }

    ListExtensions() {
        let out = {};
        Main.extensionManager.getUuids().forEach(uuid => {
            let dbusObj = this.GetExtensionInfo(uuid);
            out[uuid] = dbusObj;
        });
        return out;
    }

    GetExtensionInfo(uuid) {
        let extension = Main.extensionManager.lookup(uuid) || {};
        return ExtensionUtils.serializeExtension(extension);
    }

    GetExtensionErrors(uuid) {
        let extension = Main.extensionManager.lookup(uuid);
        if (!extension)
            return [];

        if (!extension.errors)
            return [];

        return extension.errors;
    }

    InstallRemoteExtensionAsync([uuid], invocation) {
        return ExtensionDownloader.installExtension(uuid, invocation);
    }

    UninstallExtension(uuid) {
        return ExtensionDownloader.uninstallExtension(uuid);
    }

    EnableExtension(uuid) {
        return Main.extensionManager.enableExtension(uuid);
    }

    DisableExtension(uuid) {
        return Main.extensionManager.disableExtension(uuid);
    }

    LaunchExtensionPrefs(uuid) {
        let appSys = Shell.AppSystem.get_default();
        let app = appSys.lookup_app('gnome-shell-extension-prefs.desktop');
        let info = app.get_app_info();
        let timestamp = global.display.get_current_time_roundtrip();
        info.launch_uris(['extension:///' + uuid],
                         global.create_app_launch_context(timestamp, -1));
    }

    ReloadExtension(uuid) {
        let extension = Main.extensionManager.lookup(uuid);
        if (!extension)
            return;

        Main.extensionManager.reloadExtension(extension);
    }

    CheckForUpdates() {
        ExtensionDownloader.checkForUpdates();
    }

    LoadUserExtension(uuid) {
        let extension = ExtensionUtils.extensions[uuid];
        if (extension)
            return true;

        let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid]));
        try {
            extension = ExtensionUtils.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
            ExtensionSystem.loadExtension(extension);
        } catch (e) {
            log('Could not load user extension from %s'.format(dir.get_path()));
            return false;
        }
        return true;
    }

    get ShellVersion() {
        return Config.PACKAGE_VERSION;
    }

    _extensionStateChanged(_, newState) {
        let state = ExtensionUtils.serializeExtension(newState);
        this._dbusImpl.emit_signal('ExtensionStateChanged',
            new GLib.Variant('(sa{sv})', [newState.uuid, state]));

        this._dbusImpl.emit_signal('ExtensionStatusChanged',
                                   GLib.Variant.new('(sis)', [newState.uuid, newState.state, newState.error]));
    }
};

var ScreenSaverDBus = class {
    constructor(screenShield) {
        this._screenShield = screenShield;
        screenShield.connect('active-changed', shield => {
            this._dbusImpl.emit_signal('ActiveChanged', GLib.Variant.new('(b)', [shield.active]));
        });
        screenShield.connect('wake-up-screen', shield => {
            this._dbusImpl.emit_signal('WakeUpScreen', null);
        });

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(ScreenSaverIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/ScreenSaver');

        Gio.DBus.session.own_name('org.gnome.ScreenSaver', Gio.BusNameOwnerFlags.REPLACE, null, null);
    }

    LockAsync(parameters, invocation) {
        let tmpId = this._screenShield.connect('lock-screen-shown', () => {
            this._screenShield.disconnect(tmpId);

            invocation.return_value(null);
        });

        this._screenShield.lock(true);
    }

    SetActive(active) {
        if (active)
            this._screenShield.activate(true);
        else
            this._screenShield.deactivate(false);
    }

    GetActive() {
        return this._screenShield.active;
    }

    GetActiveTime() {
        let started = this._screenShield.activationTime;
        if (started > 0)
            return Math.floor((GLib.get_monotonic_time() - started) / 1000000);
        else
            return 0;
    }
};
(uuay)boxpointer.js�[// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Meta, Shell, St } = imports.gi;

const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var PopupAnimation = {
    NONE:  0,
    SLIDE: 1 << 0,
    FADE:  1 << 1,
    FULL:  ~0,
};

var POPUP_ANIMATION_TIME = 0.15;

/**
 * BoxPointer:
 * @side: side to draw the arrow on
 * @binProperties: Properties to set on contained bin
 *
 * An actor which displays a triangle "arrow" pointing to a given
 * side.  The .bin property is a container in which content can be
 * placed.  The arrow position may be controlled via
 * setArrowOrigin(). The arrow side might be temporarily flipped
 * depending on the box size and source position to keep the box
 * totally inside the monitor workarea if possible.
 *
 */
var BoxPointer = GObject.registerClass({
    Signals: { 'arrow-side-changed': {} },
}, class BoxPointer extends St.Widget {
    _init(arrowSide, binProperties) {
        super._init();

        this.actor = this;

        this.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);

        this._arrowSide = arrowSide;
        this._userArrowSide = arrowSide;
        this._arrowOrigin = 0;
        this._arrowActor = null;
        this.bin = new St.Bin(binProperties);
        this.add_actor(this.bin);
        this._border = new St.DrawingArea();
        this._border.connect('repaint', this._drawBorder.bind(this));
        this.add_actor(this._border);
        this.bin.raise(this._border);
        this._sourceAlignment = 0.5;
        this._capturedEventId = 0;
        this._muteInput();
    }

    get arrowSide() {
        return this._arrowSide;
    }

    _muteInput() {
        if (this._capturedEventId == 0)
            this._capturedEventId = this.connect('captured-event',
                                                 () => Clutter.EVENT_STOP);
    }

    _unmuteInput() {
        if (this._capturedEventId != 0) {
            this.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
    }

    // BoxPointer.show() and BoxPointer.hide() are here for only compatibility
    // purposes, and will be removed in 3.32.
    show(animate, onComplete) {
        if (animate !== undefined) {
            try {
                throw new Error('BoxPointer.show() has been moved to BoxPointer.open(), this code will break in the future.');
            } catch(e) {
                logError(e);
                this.open(animate, onComplete);
                return;
            }
        }

        this.visible = true;
    }

    hide(animate, onComplete) {
        if (animate !== undefined) {
            try {
                throw new Error('BoxPointer.hide() has been moved to BoxPointer.close(), this code will break in the future.');
            } catch(e) {
                logError(e);
                this.close(animate, onComplete);
                return;
            }
        }

        this.visible = false;
    }

    open(animate, onComplete) {
        let themeNode = this.get_theme_node();
        let rise = themeNode.get_length('-arrow-rise');
        let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;

        if (animate & PopupAnimation.FADE)
            this.opacity = 0;
        else
            this.opacity = 255;

        this.show();

        if (animate & PopupAnimation.SLIDE) {
            switch (this._arrowSide) {
                case St.Side.TOP:
                    this.translation_y = -rise;
                    break;
                case St.Side.BOTTOM:
                    this.translation_y = rise;
                    break;
                case St.Side.LEFT:
                    this.translation_x = -rise;
                    break;
                case St.Side.RIGHT:
                    this.translation_x = rise;
                    break;
            }
        }

        Tweener.addTween(this, { opacity: 255,
                                 translation_x: 0,
                                 translation_y: 0,
                                 transition: 'linear',
                                 onComplete: () => {
                                     this._unmuteInput();
                                     if (onComplete)
                                         onComplete();
                                 },
                                 time: animationTime });
    }

    close(animate, onComplete) {
        if (!this.visible)
            return;

        let translationX = 0;
        let translationY = 0;
        let themeNode = this.get_theme_node();
        let rise = themeNode.get_length('-arrow-rise');
        let fade = (animate & PopupAnimation.FADE);
        let animationTime = (animate & PopupAnimation.FULL) ? POPUP_ANIMATION_TIME : 0;

        if (animate & PopupAnimation.SLIDE) {
            switch (this._arrowSide) {
                case St.Side.TOP:
                    translationY = rise;
                    break;
                case St.Side.BOTTOM:
                    translationY = -rise;
                    break;
                case St.Side.LEFT:
                    translationX = rise;
                    break;
                case St.Side.RIGHT:
                    translationX = -rise;
                    break;
            }
        }

        this._muteInput();

        Tweener.removeTweens(this);
        Tweener.addTween(this, { opacity: fade ? 0 : 255,
                                 translation_x: translationX,
                                 translation_y: translationY,
                                 transition: 'linear',
                                 time: animationTime,
                                 onComplete: () => {
                                     this.hide();
                                     this.opacity = 0;
                                     this.translation_x = 0;
                                     this.translation_y = 0;
                                     if (onComplete)
                                         onComplete();
                                 }
                               });
    }

    _adjustAllocationForArrow(isWidth, minSize, natSize) {
        let themeNode = this.get_theme_node();
        let borderWidth = themeNode.get_length('-arrow-border-width');
        minSize += borderWidth * 2;
        natSize += borderWidth * 2;
        if ((!isWidth && (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM))
            || (isWidth && (this._arrowSide == St.Side.LEFT || this._arrowSide == St.Side.RIGHT))) {
            let rise = themeNode.get_length('-arrow-rise');
            minSize += rise;
            natSize += rise;
        }

        return [minSize, natSize];
    }

    vfunc_get_preferred_width(forHeight) {
        let themeNode = this.get_theme_node();
        forHeight = themeNode.adjust_for_height(forHeight);

        let width = this.bin.get_preferred_width(forHeight);
        width = this._adjustAllocationForArrow(true, ...width);

        return themeNode.adjust_preferred_width(...width);
    }

    vfunc_get_preferred_height(forWidth) {
        let themeNode = this.get_theme_node();
        let borderWidth = themeNode.get_length('-arrow-border-width');
        forWidth = themeNode.adjust_for_width(forWidth);

        let height = this.bin.get_preferred_height(forWidth - 2 * borderWidth);
        height = this._adjustAllocationForArrow(false, ...height);

        return themeNode.adjust_preferred_height(...height);
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let themeNode = this.get_theme_node();
        box = themeNode.get_content_box(box);

        let borderWidth = themeNode.get_length('-arrow-border-width');
        let rise = themeNode.get_length('-arrow-rise');
        let childBox = new Clutter.ActorBox();
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;

        childBox.x1 = 0;
        childBox.y1 = 0;
        childBox.x2 = availWidth;
        childBox.y2 = availHeight;
        this._border.allocate(childBox, flags);

        childBox.x1 = borderWidth;
        childBox.y1 = borderWidth;
        childBox.x2 = availWidth - borderWidth;
        childBox.y2 = availHeight - borderWidth;
        switch (this._arrowSide) {
            case St.Side.TOP:
                childBox.y1 += rise;
                break;
            case St.Side.BOTTOM:
                childBox.y2 -= rise;
                break;
            case St.Side.LEFT:
                childBox.x1 += rise;
                break;
            case St.Side.RIGHT:
                childBox.x2 -= rise;
                break;
        }
        this.bin.allocate(childBox, flags);

        if (this._sourceActor && this._sourceActor.mapped) {
            this._reposition();
            this._updateFlip();
        }
    }

    _drawBorder(area) {
        let themeNode = this.get_theme_node();

        if (this._arrowActor) {
            let [sourceX, sourceY] = this._arrowActor.get_transformed_position();
            let [sourceWidth, sourceHeight] = this._arrowActor.get_transformed_size();
            let [absX, absY] = this.get_transformed_position();

            if (this._arrowSide == St.Side.TOP ||
                this._arrowSide == St.Side.BOTTOM) {
                this._arrowOrigin = sourceX - absX + sourceWidth / 2;
            } else {
                this._arrowOrigin = sourceY - absY + sourceHeight / 2;
            }
        }

        let borderWidth = themeNode.get_length('-arrow-border-width');
        let base = themeNode.get_length('-arrow-base');
        let rise = themeNode.get_length('-arrow-rise');
        let borderRadius = themeNode.get_length('-arrow-border-radius');

        let halfBorder = borderWidth / 2;
        let halfBase = Math.floor(base/2);

        let backgroundColor = themeNode.get_color('-arrow-background-color');

        let [width, height] = area.get_surface_size();
        let [boxWidth, boxHeight] = [width, height];
        if (this._arrowSide == St.Side.TOP || this._arrowSide == St.Side.BOTTOM) {
            boxHeight -= rise;
        } else {
            boxWidth -= rise;
        }
        let cr = area.get_context();

        // Translate so that box goes from 0,0 to boxWidth,boxHeight,
        // with the arrow poking out of that
        if (this._arrowSide == St.Side.TOP) {
            cr.translate(0, rise);
        } else if (this._arrowSide == St.Side.LEFT) {
            cr.translate(rise, 0);
        }

        let [x1, y1] = [halfBorder, halfBorder];
        let [x2, y2] = [boxWidth - halfBorder, boxHeight - halfBorder];

        let skipTopLeft = false;
        let skipTopRight = false;
        let skipBottomLeft = false;
        let skipBottomRight = false;

        if (rise) {
            switch (this._arrowSide) {
            case St.Side.TOP:
                if (this._arrowOrigin == x1)
                    skipTopLeft = true;
                else if (this._arrowOrigin == x2)
                    skipTopRight = true;
                break;

            case St.Side.RIGHT:
                if (this._arrowOrigin == y1)
                    skipTopRight = true;
                else if (this._arrowOrigin == y2)
                    skipBottomRight = true;
                break;

            case St.Side.BOTTOM:
                if (this._arrowOrigin == x1)
                    skipBottomLeft = true;
                else if (this._arrowOrigin == x2)
                    skipBottomRight = true;
                break;

            case St.Side.LEFT:
                if (this._arrowOrigin == y1)
                    skipTopLeft = true;
                else if (this._arrowOrigin == y2)
                    skipBottomLeft = true;
                break;
            }
        }

        cr.moveTo(x1 + borderRadius, y1);
        if (this._arrowSide == St.Side.TOP && rise) {
            if (skipTopLeft) {
                cr.moveTo(x1, y2 - borderRadius);
                cr.lineTo(x1, y1 - rise);
                cr.lineTo(x1 + halfBase, y1);
            } else if (skipTopRight) {
                cr.lineTo(x2 - halfBase, y1);
                cr.lineTo(x2, y1 - rise);
                cr.lineTo(x2, y1 + borderRadius);
            } else {
                cr.lineTo(this._arrowOrigin - halfBase, y1);
                cr.lineTo(this._arrowOrigin, y1 - rise);
                cr.lineTo(this._arrowOrigin + halfBase, y1);
            }
        }

        if (!skipTopRight) {
            cr.lineTo(x2 - borderRadius, y1);
            cr.arc(x2 - borderRadius, y1 + borderRadius, borderRadius,
                   3*Math.PI/2, Math.PI*2);
        }

        if (this._arrowSide == St.Side.RIGHT && rise) {
            if (skipTopRight) {
                cr.lineTo(x2 + rise, y1);
                cr.lineTo(x2 + rise, y1 + halfBase);
            } else if (skipBottomRight) {
                cr.lineTo(x2, y2 - halfBase);
                cr.lineTo(x2 + rise, y2);
                cr.lineTo(x2 - borderRadius, y2);
            } else {
                cr.lineTo(x2, this._arrowOrigin - halfBase);
                cr.lineTo(x2 + rise, this._arrowOrigin);
                cr.lineTo(x2, this._arrowOrigin + halfBase);
            }
        }

        if (!skipBottomRight) {
            cr.lineTo(x2, y2 - borderRadius);
            cr.arc(x2 - borderRadius, y2 - borderRadius, borderRadius,
                   0, Math.PI/2);
        }

        if (this._arrowSide == St.Side.BOTTOM && rise) {
            if (skipBottomLeft) {
                cr.lineTo(x1 + halfBase, y2);
                cr.lineTo(x1, y2 + rise);
                cr.lineTo(x1, y2 - borderRadius);
            } else if (skipBottomRight) {
                cr.lineTo(x2, y2 + rise);
                cr.lineTo(x2 - halfBase, y2);
            } else {
                cr.lineTo(this._arrowOrigin + halfBase, y2);
                cr.lineTo(this._arrowOrigin, y2 + rise);
                cr.lineTo(this._arrowOrigin - halfBase, y2);
            }
        }

        if (!skipBottomLeft) {
            cr.lineTo(x1 + borderRadius, y2);
            cr.arc(x1 + borderRadius, y2 - borderRadius, borderRadius,
                   Math.PI/2, Math.PI);
        }

        if (this._arrowSide == St.Side.LEFT && rise) {
            if (skipTopLeft) {
                cr.lineTo(x1, y1 + halfBase);
                cr.lineTo(x1 - rise, y1);
                cr.lineTo(x1 + borderRadius, y1);
            } else if (skipBottomLeft) {
                cr.lineTo(x1 - rise, y2)
                cr.lineTo(x1 - rise, y2 - halfBase);
            } else {
                cr.lineTo(x1, this._arrowOrigin + halfBase);
                cr.lineTo(x1 - rise, this._arrowOrigin);
                cr.lineTo(x1, this._arrowOrigin - halfBase);
            }
        }

        if (!skipTopLeft) {
            cr.lineTo(x1, y1 + borderRadius);
            cr.arc(x1 + borderRadius, y1 + borderRadius, borderRadius,
                   Math.PI, 3*Math.PI/2);
        }

        Clutter.cairo_set_source_color(cr, backgroundColor);
        cr.fillPreserve();

        if (borderWidth > 0) {
            let borderColor = themeNode.get_color('-arrow-border-color');
            Clutter.cairo_set_source_color(cr, borderColor);
            cr.setLineWidth(borderWidth);
            cr.stroke();
        }

        cr.$dispose();
    }

    setPosition(sourceActor, alignment) {
        // We need to show it now to force an allocation,
        // so that we can query the correct size.
        this.show();

        this._sourceActor = sourceActor;
        this._arrowAlignment = alignment;

        this._reposition();
        this._updateFlip();
    }

    setSourceAlignment(alignment) {
        this._sourceAlignment = alignment;

        if (!this._sourceActor)
            return;

        this.setPosition(this._sourceActor, this._arrowAlignment);
    }

    _reposition() {
        let sourceActor = this._sourceActor;
        let alignment = this._arrowAlignment;
        let monitorIndex = Main.layoutManager.findIndexForActor(sourceActor);

        this._sourceAllocation = Shell.util_get_transformed_allocation(sourceActor);
        this._workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);

        // Position correctly relative to the sourceActor
        let sourceNode = sourceActor.get_theme_node();
        let sourceContentBox = sourceNode.get_content_box(sourceActor.get_allocation_box());
        let sourceAllocation = this._sourceAllocation;
        let sourceCenterX = sourceAllocation.x1 + sourceContentBox.x1 + (sourceContentBox.x2 - sourceContentBox.x1) * this._sourceAlignment;
        let sourceCenterY = sourceAllocation.y1 + sourceContentBox.y1 + (sourceContentBox.y2 - sourceContentBox.y1) * this._sourceAlignment;
        let [minWidth, minHeight, natWidth, natHeight] = this.get_preferred_size();

        // We also want to keep it onscreen, and separated from the
        // edge by the same distance as the main part of the box is
        // separated from its sourceActor
        let workarea = this._workArea;
        let themeNode = this.get_theme_node();
        let borderWidth = themeNode.get_length('-arrow-border-width');
        let arrowBase = themeNode.get_length('-arrow-base');
        let borderRadius = themeNode.get_length('-arrow-border-radius');
        let margin = (4 * borderRadius + borderWidth + arrowBase);

        let gap = themeNode.get_length('-boxpointer-gap');
        let padding = themeNode.get_length('-arrow-rise');

        let resX, resY;

        switch (this._arrowSide) {
        case St.Side.TOP:
            resY = sourceAllocation.y2 + gap;
            break;
        case St.Side.BOTTOM:
            resY = sourceAllocation.y1 - natHeight - gap;
            break;
        case St.Side.LEFT:
            resX = sourceAllocation.x2 + gap;
            break;
        case St.Side.RIGHT:
            resX = sourceAllocation.x1 - natWidth - gap;
            break;
        }

        // Now align and position the pointing axis, making sure it fits on
        // screen. If the arrowOrigin is so close to the edge that the arrow
        // will not be isosceles, we try to compensate as follows:
        //   - We skip the rounded corner and settle for a right angled arrow
        //     as shown below. See _drawBorder for further details.
        //     |\_____
        //     |
        //     |
        //   - If the arrow was going to be acute angled, we move the position
        //     of the box to maintain the arrow's accuracy.

        let arrowOrigin;
        let halfBase = Math.floor(arrowBase/2);
        let halfBorder = borderWidth / 2;
        let halfMargin = margin / 2;
        let [x1, y1] = [halfBorder, halfBorder];
        let [x2, y2] = [natWidth - halfBorder, natHeight - halfBorder];

        switch (this._arrowSide) {
        case St.Side.TOP:
        case St.Side.BOTTOM:
            resX = sourceCenterX - (halfMargin + (natWidth - margin) * alignment);

            resX = Math.max(resX, workarea.x + padding);
            resX = Math.min(resX, workarea.x + workarea.width - (padding + natWidth));

            arrowOrigin = sourceCenterX - resX;
            if (arrowOrigin <= (x1 + (borderRadius + halfBase))) {
                if (arrowOrigin > x1)
                    resX += (arrowOrigin - x1);
                arrowOrigin = x1;
            } else if (arrowOrigin >= (x2 - (borderRadius + halfBase))) {
                if (arrowOrigin < x2)
                    resX -= (x2 - arrowOrigin);
                arrowOrigin = x2;
            }
            break;

        case St.Side.LEFT:
        case St.Side.RIGHT:
            resY = sourceCenterY - (halfMargin + (natHeight - margin) * alignment);

            resY = Math.max(resY, workarea.y + padding);
            resY = Math.min(resY, workarea.y + workarea.height - (padding + natHeight));

            arrowOrigin = sourceCenterY - resY;
            if (arrowOrigin <= (y1 + (borderRadius + halfBase))) {
                if (arrowOrigin > y1)
                    resY += (arrowOrigin - y1);
                arrowOrigin = y1;
            } else if (arrowOrigin >= (y2 - (borderRadius + halfBase))) {
                if (arrowOrigin < y2)
                    resX -= (y2 - arrowOrigin);
                arrowOrigin = y2;
            }
            break;
        }

        this.setArrowOrigin(arrowOrigin);

        let parent = this.get_parent();
        let success, x, y;
        while (!success) {
            [success, x, y] = parent.transform_stage_point(resX, resY);
            parent = parent.get_parent();
        }

        // Actually set the position
        this.x = Math.floor(x);
        this.y = Math.floor(y);
    }

    // @origin: Coordinate specifying middle of the arrow, along
    // the Y axis for St.Side.LEFT, St.Side.RIGHT from the top and X axis from
    // the left for St.Side.TOP and St.Side.BOTTOM.
    setArrowOrigin(origin) {
        if (this._arrowOrigin != origin) {
            this._arrowOrigin = origin;
            this._border.queue_repaint();
        }
    }

    // @actor: an actor relative to which the arrow is positioned.
    // Differently from setPosition, this will not move the boxpointer itself,
    // on the arrow
    setArrowActor(actor) {
        if (this._arrowActor != actor) {
            this._arrowActor = actor;
            this._border.queue_repaint();
        }
    }

    _calculateArrowSide(arrowSide) {
        let sourceAllocation = this._sourceAllocation;
        let [minWidth, minHeight, boxWidth, boxHeight] = this.get_preferred_size();
        let workarea = this._workArea;

        switch (arrowSide) {
        case St.Side.TOP:
            if (sourceAllocation.y2 + boxHeight > workarea.y + workarea.height &&
                boxHeight < sourceAllocation.y1 - workarea.y)
                return St.Side.BOTTOM;
            break;
        case St.Side.BOTTOM:
            if (sourceAllocation.y1 - boxHeight < workarea.y &&
                boxHeight < workarea.y + workarea.height - sourceAllocation.y2)
                return St.Side.TOP;
            break;
        case St.Side.LEFT:
            if (sourceAllocation.x2 + boxWidth > workarea.x + workarea.width &&
                boxWidth < sourceAllocation.x1 - workarea.x)
                return St.Side.RIGHT;
            break;
        case St.Side.RIGHT:
            if (sourceAllocation.x1 - boxWidth < workarea.x &&
                boxWidth < workarea.x + workarea.width - sourceAllocation.x2)
                return St.Side.LEFT;
            break;
        }

        return arrowSide;
    }

    _updateFlip() {
        let arrowSide = this._calculateArrowSide(this._userArrowSide);
        if (this._arrowSide != arrowSide) {
            this._arrowSide = arrowSide;
            this._reposition();
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this.queue_relayout();
                return false;
            });

            this.emit('arrow-side-changed');
        }
    }

    updateArrowSide(side) {
        this._arrowSide = side;
        this._border.queue_repaint();

        this.emit('arrow-side-changed');
    }

    getPadding(side) {
        return this.bin.get_theme_node().get_padding(side);
    }

    getArrowHeight() {
        return this.get_theme_node().get_length('-arrow-rise');
    }
});
(uuay)xdndHandler.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Meta } = imports.gi;
const Signals = imports.signals;

const DND = imports.ui.dnd;
const Main = imports.ui.main;

var XdndHandler = class {
    constructor() {
        // Used to display a clone of the cursor window when the
        // window group is hidden (like it happens in the overview)
        this._cursorWindowClone = null;

        // Used as a drag actor in case we don't have a cursor window clone
        this._dummy = new Clutter.Actor({ width: 1, height: 1, opacity: 0 });
        Main.uiGroup.add_actor(this._dummy);
        this._dummy.hide();

        if (!Meta.is_wayland_compositor())
            global.init_xdnd();

        var dnd = Meta.get_backend().get_dnd();
        dnd.connect('dnd-enter', this._onEnter.bind(this));
        dnd.connect('dnd-position-change', this._onPositionChanged.bind(this));
        dnd.connect('dnd-leave', this._onLeave.bind(this));

        this._windowGroupVisibilityHandlerId = 0;
    }

    // Called when the user cancels the drag (i.e release the button)
    _onLeave() {
        if (this._windowGroupVisibilityHandlerId != 0) {
            global.window_group.disconnect(this._windowGroupVisibilityHandlerId);
            this._windowGroupVisibilityHandlerId = 0;
        }
        if (this._cursorWindowClone) {
            this._cursorWindowClone.destroy();
            this._cursorWindowClone = null;
        }

        this.emit('drag-end');
    }

    _onEnter() {
        this._windowGroupVisibilityHandlerId  =
                global.window_group.connect('notify::visible',
                    this._onWindowGroupVisibilityChanged.bind(this));

        this.emit('drag-begin', global.get_current_time());
    }

    _onWindowGroupVisibilityChanged() {
        if (!global.window_group.visible) {
            if (this._cursorWindowClone)
                return;

            let windows = global.get_window_actors();
            let cursorWindow = windows[windows.length - 1];

            // FIXME: more reliable way?
            if (!cursorWindow.get_meta_window().is_override_redirect())
                return;

            let constraint_position = new Clutter.BindConstraint({ coordinate : Clutter.BindCoordinate.POSITION,
                                                                   source: cursorWindow});

            this._cursorWindowClone = new Clutter.Clone({ source: cursorWindow });
            Main.uiGroup.add_actor(this._cursorWindowClone);

            // Make sure that the clone has the same position as the source
            this._cursorWindowClone.add_constraint(constraint_position);
        } else {
            if (this._cursorWindowClone) {
                this._cursorWindowClone.destroy();
                this._cursorWindowClone = null;
            }
        }
    }

    _onPositionChanged(obj, x, y) {
        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);

        // Make sure that the cursor window is on top
        if (this._cursorWindowClone)
             this._cursorWindowClone.raise_top();

        let dragEvent = {
            x: x,
            y: y,
            dragActor: this._cursorWindowClone ? this._cursorWindowClone : this._dummy,
            source: this,
            targetActor: pickedActor
        };

        for (let i = 0; i < DND.dragMonitors.length; i++) {
            let motionFunc = DND.dragMonitors[i].dragMotion;
            if (motionFunc) {
                let result = motionFunc(dragEvent);
                if (result != DND.DragMotionResult.CONTINUE)
                    return;
            }
        }

        while (pickedActor) {
                if (pickedActor._delegate && pickedActor._delegate.handleDragOver) {
                    let [r, targX, targY] = pickedActor.transform_stage_point(x, y);
                    let result = pickedActor._delegate.handleDragOver(this,
                                                                      dragEvent.dragActor,
                                                                      targX,
                                                                      targY,
                                                                      global.get_current_time());
                    if (result != DND.DragMotionResult.CONTINUE)
                        return;
                }
                pickedActor = pickedActor.get_parent();
        }
    }
};
Signals.addSignalMethods(XdndHandler.prototype);
(uuay)ctrlAltTab.jsU// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Meta, Shell, St } = imports.gi;

const Main = imports.ui.main;
const SwitcherPopup = imports.ui.switcherPopup;
const Params = imports.misc.params;

var POPUP_APPICON_SIZE = 96;
var POPUP_FADE_TIME = 0.1; // seconds

var SortGroup = {
    TOP:    0,
    MIDDLE: 1,
    BOTTOM: 2
};

var CtrlAltTabManager = class CtrlAltTabManager {
    constructor() {
        this._items = [];
        this.addGroup(global.window_group, _("Windows"),
                      'focus-windows-symbolic', { sortGroup: SortGroup.TOP,
                                                  focusCallback: this._focusWindows.bind(this) });
    }

    addGroup(root, name, icon, params) {
        let item = Params.parse(params, { sortGroup: SortGroup.MIDDLE,
                                          proxy: root,
                                          focusCallback: null });

        item.root = root;
        item.name = name;
        item.iconName = icon;

        this._items.push(item);
        root.connect('destroy', () => { this.removeGroup(root); });
        if (root instanceof St.Widget)
            global.focus_manager.add_group(root);
    }

    removeGroup(root) {
        if (root instanceof St.Widget)
            global.focus_manager.remove_group(root);
        for (let i = 0; i < this._items.length; i++) {
            if (this._items[i].root == root) {
                this._items.splice(i, 1);
                return;
            }
        }
    }

    focusGroup(item, timestamp) {
        if (item.focusCallback)
            item.focusCallback(timestamp);
        else
            item.root.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
    }

    // Sort the items into a consistent order; panel first, tray last,
    // and everything else in between, sorted by X coordinate, so that
    // they will have the same left-to-right ordering in the
    // Ctrl-Alt-Tab dialog as they do onscreen.
    _sortItems(a, b) {
        if (a.sortGroup != b.sortGroup)
            return a.sortGroup - b.sortGroup;

        let ax, bx, y;
        [ax, y] = a.proxy.get_transformed_position();
        [bx, y] = b.proxy.get_transformed_position();

        return ax - bx;
    }

    popup(backward, binding, mask) {
        // Start with the set of focus groups that are currently mapped
        let items = this._items.filter(item => item.proxy.mapped);

        // And add the windows metacity would show in its Ctrl-Alt-Tab list
        if (Main.sessionMode.hasWindows && !Main.overview.visible) {
            let display = global.display;
            let workspaceManager = global.workspace_manager;
            let activeWorkspace = workspaceManager.get_active_workspace();
            let windows = display.get_tab_list(Meta.TabList.DOCKS,
                                               activeWorkspace);
            let windowTracker = Shell.WindowTracker.get_default();
            let textureCache = St.TextureCache.get_default();
            for (let i = 0; i < windows.length; i++) {
                let icon = null;
                let iconName = null;
                if (windows[i].get_window_type () == Meta.WindowType.DESKTOP) {
                    iconName = 'video-display-symbolic';
                } else {
                    let app = windowTracker.get_window_app(windows[i]);
                    if (app)
                        icon = app.create_icon_texture(POPUP_APPICON_SIZE);
                    else
                        icon = textureCache.bind_cairo_surface_property(windows[i],
                                                                        'icon',
                                                                        POPUP_APPICON_SIZE);
                }

                items.push({ name: windows[i].title,
                             proxy: windows[i].get_compositor_private(),
                             focusCallback: function(timestamp) {
                                 Main.activateWindow(this, timestamp);
                             }.bind(windows[i]),
                             iconActor: icon,
                             iconName: iconName,
                             sortGroup: SortGroup.MIDDLE });
            }
        }

        if (!items.length)
            return;

        items.sort(this._sortItems.bind(this));

        if (!this._popup) {
            this._popup = new CtrlAltTabPopup(items);
            this._popup.show(backward, binding, mask);

            this._popup.connect('destroy',
                                () => {
                                    this._popup = null;
                                });
        }
    }

    _focusWindows(timestamp) {
        global.display.focus_default_window(timestamp);
    }
};

var CtrlAltTabPopup = GObject.registerClass(
class CtrlAltTabPopup extends SwitcherPopup.SwitcherPopup {
    _init(items) {
        super._init(items);

        this._switcherList = new CtrlAltTabSwitcher(this._items);
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.SWITCH_PANELS)
            this._select(this._next());
        else if (action == Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD)
            this._select(this._previous());
        else if (keysym == Clutter.Left)
            this._select(this._previous());
        else if (keysym == Clutter.Right)
            this._select(this._next());
        else
            return Clutter.EVENT_PROPAGATE;

        return Clutter.EVENT_STOP;
    }

    _finish(time) {
        super._finish(time);
        Main.ctrlAltTabManager.focusGroup(this._items[this._selectedIndex], time);
    }
});

var CtrlAltTabSwitcher = GObject.registerClass(
class CtrlAltTabSwitcher extends SwitcherPopup.SwitcherList {
    _init(items) {
        super._init(true);

        for (let i = 0; i < items.length; i++)
            this._addIcon(items[i]);
    }

    _addIcon(item) {
        let box = new St.BoxLayout({ style_class: 'alt-tab-app',
                                     vertical: true });

        let icon = item.iconActor;
        if (!icon) {
            icon = new St.Icon({ icon_name: item.iconName,
                                 icon_size: POPUP_APPICON_SIZE });
        }
        box.add(icon, { x_fill: false, y_fill: false } );

        let text = new St.Label({ text: item.name });
        box.add(text, { x_fill: false });

        this.addItem(box, text);
    }
});
(uuay)permissionStore.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;

const { loadInterfaceXML } = imports.misc.fileUtils;

const PermissionStoreIface = loadInterfaceXML('org.freedesktop.impl.portal.PermissionStore');
const PermissionStoreProxy = Gio.DBusProxy.makeProxyWrapper(PermissionStoreIface);

function PermissionStore(initCallback, cancellable) {
    return new PermissionStoreProxy(Gio.DBus.session,
                                    'org.freedesktop.impl.portal.PermissionStore',
                                    '/org/freedesktop/impl/portal/PermissionStore',
                                    initCallback, cancellable);
};
(uuay)jsParse.js�/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */

// Returns a list of potential completions for text. Completions either
// follow a dot (e.g. foo.ba -> bar) or they are picked from globalCompletionList (e.g. fo -> foo)
// commandHeader is prefixed on any expression before it is eval'ed.  It will most likely
// consist of global constants that might not carry over from the calling environment.
//
// This function is likely the one you want to call from external modules
function getCompletions(text, commandHeader, globalCompletionList) {
    let methods = [];
    let expr, base;
    let attrHead = '';
    if (globalCompletionList == null) {
        globalCompletionList = [];
    }

    let offset = getExpressionOffset(text, text.length - 1);
    if (offset >= 0) {
        text = text.slice(offset);

        // Look for expressions like "Main.panel.foo" and match Main.panel and foo
        let matches = text.match(/(.*)\.(.*)/);
        if (matches) {
            [expr, base, attrHead] = matches;

            methods = getPropertyNamesFromExpression(base, commandHeader).filter(
                attr => attr.slice(0, attrHead.length) == attrHead
            );
        }

        // Look for the empty expression or partially entered words
        // not proceeded by a dot and match them against global constants
        matches = text.match(/^(\w*)$/);
        if (text == '' || matches) {
            [expr, attrHead] = matches;
            methods = globalCompletionList.filter(
                attr => attr.slice(0, attrHead.length) == attrHead
            );
        }
    }

    return [methods, attrHead];
}


//
// A few functions for parsing strings of javascript code.
//

// Identify characters that delimit an expression.  That is,
// if we encounter anything that isn't a letter, '.', ')', or ']',
// we should stop parsing.
function isStopChar(c) {
    return !c.match(/[\w\.\)\]]/);
}

// Given the ending position of a quoted string, find where it starts
function findMatchingQuote(expr, offset) {
    let quoteChar = expr.charAt(offset);
    for (let i = offset - 1; i >= 0; --i) {
        if (expr.charAt(i) == quoteChar && expr.charAt(i-1) != '\\'){
            return i;
        }
    }
    return -1;
}

// Given the ending position of a regex, find where it starts
function findMatchingSlash(expr, offset) {
    for (let i = offset - 1; i >= 0; --i) {
        if (expr.charAt(i) == '/' && expr.charAt(i-1) != '\\'){
            return i;
        }
    }
    return -1;
}

// If expr.charAt(offset) is ')' or ']',
// return the position of the corresponding '(' or '[' bracket.
// This function does not check for syntactic correctness.  e.g.,
// findMatchingBrace("[(])", 3) returns 1.
function findMatchingBrace(expr, offset) {
    let closeBrace = expr.charAt(offset);
    let openBrace = ({')': '(', ']': '['})[closeBrace];

    function findTheBrace(expr, offset) {
        if (offset < 0) {
            return -1;
        }

        if (expr.charAt(offset) == openBrace) {
            return offset;
        }
        if (expr.charAt(offset).match(/['"]/)) {
            return findTheBrace(expr, findMatchingQuote(expr, offset) - 1);
        }
        if (expr.charAt(offset) == '/') {
            return findTheBrace(expr, findMatchingSlash(expr, offset) - 1);
        }
        if (expr.charAt(offset) == closeBrace) {
            return findTheBrace(expr, findTheBrace(expr, offset - 1) - 1);
        }

        return findTheBrace(expr, offset - 1);

    }

    return findTheBrace(expr, offset - 1);
}

// Walk expr backwards from offset looking for the beginning of an
// expression suitable for passing to eval.
// There is no guarantee of correct javascript syntax between the return
// value and offset.  This function is meant to take a string like
// "foo(Obj.We.Are.Completing" and allow you to extract "Obj.We.Are.Completing"
function getExpressionOffset(expr, offset) {
    while (offset >= 0) {
        let currChar = expr.charAt(offset);

        if (isStopChar(currChar)){
            return offset + 1;
        }

        if (currChar.match(/[\)\]]/)) {
            offset = findMatchingBrace(expr, offset);
        }

        --offset;
    }

    return offset + 1;
}

// Things with non-word characters or that start with a number
// are not accessible via .foo notation and so aren't returned
function isValidPropertyName(w) {
    return !(w.match(/\W/) || w.match(/^\d/));
}

// To get all properties (enumerable and not), we need to walk
// the prototype chain ourselves
function getAllProps(obj) {
    if (obj === null || obj === undefined) {
        return [];
    }
    return Object.getOwnPropertyNames(obj).concat( getAllProps(Object.getPrototypeOf(obj)) );
}

// Given a string _expr_, returns all methods
// that can be accessed via '.' notation.
// e.g., expr="({ foo: null, bar: null, 4: null })" will
// return ["foo", "bar", ...] but the list will not include "4",
// since methods accessed with '.' notation must star with a letter or _.
function getPropertyNamesFromExpression(expr, commandHeader) {
    if (commandHeader == null) {
        commandHeader = '';
    }

    let obj = {};
    if (!isUnsafeExpression(expr)) {
        try {
                obj = eval(commandHeader + expr);
        } catch (e) {
            return [];
        }
    } else {
        return [];
    }

    let propsUnique = {};
    if (typeof obj === 'object'){
        let allProps = getAllProps(obj);
        // Get only things we are allowed to complete following a '.'
        allProps = allProps.filter( isValidPropertyName );

        // Make sure propsUnique contains one key for every
        // property so we end up with a unique list of properties
        allProps.map(p => propsUnique[p] = null);
    }
    return Object.keys(propsUnique).sort();
}

// Given a list of words, returns the longest prefix they all have in common
function getCommonPrefix(words) {
    let word = words[0];
    for (let i = 0; i < word.length; i++) {
        for (let w = 1; w < words.length; w++) {
            if (words[w].charAt(i) != word.charAt(i))
                return word.slice(0, i);
        }
    }
    return word;
}

// Returns true if there is reason to think that eval(str)
// will modify the global scope
function isUnsafeExpression(str) {
    // Remove any blocks that are quoted or are in a regex
    function removeLiterals(str) {
        if (str.length == 0) {
            return '';
        }

        let currChar = str.charAt(str.length - 1);
        if (currChar == '"' || currChar == '\'') {
            return removeLiterals(str.slice(0, findMatchingQuote(str, str.length - 1)));
        } else if (currChar == '/') {
            return removeLiterals(str.slice(0, findMatchingSlash(str, str.length - 1)));
        }

        return removeLiterals(str.slice(0, str.length - 1)) + currChar;
    }

    // Check for any sort of assignment
    // The strategy used is dumb: remove any quotes
    // or regexs and comparison operators and see if there is an '=' character.
    // If there is, it might be an unsafe assignment.

    let prunedStr = removeLiterals(str);
    prunedStr = prunedStr.replace(/[=!]==/g, '');    //replace === and !== with nothing
    prunedStr = prunedStr.replace(/[=<>!]=/g, '');    //replace ==, <=, >=, != with nothing

    if (prunedStr.match(/=/)) {
        return true;
    } else if (prunedStr.match(/;/)) {
        // If we contain a semicolon not inside of a quote/regex, assume we're unsafe as well
        return true;
    }

    return false;
}

// Returns a list of global keywords derived from str
function getDeclaredConstants(str) {
    let ret = [];
    str.split(';').forEach(s => {
        let base, keyword;
        let match = s.match(/const\s+(\w+)\s*=/);
        if (match) {
            [base, keyword] = match;
            ret.push(keyword);
        }
    });

    return ret;
}
(uuay)viewSelector.jsU// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GObject, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;

const AppDisplay = imports.ui.appDisplay;
const Main = imports.ui.main;
const OverviewControls = imports.ui.overviewControls;
const Params = imports.misc.params;
const Search = imports.ui.search;
const ShellEntry = imports.ui.shellEntry;
const Tweener = imports.ui.tweener;
const WorkspacesView = imports.ui.workspacesView;
const EdgeDragAction = imports.ui.edgeDragAction;
const IconGrid = imports.ui.iconGrid;

const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
var PINCH_GESTURE_THRESHOLD = 0.7;

var ViewPage = {
    WINDOWS: 1,
    APPS: 2,
    SEARCH: 3
};

var FocusTrap = GObject.registerClass(
class FocusTrap extends St.Widget {
    vfunc_navigate_focus(from, direction) {
        if (direction == St.DirectionType.TAB_FORWARD ||
            direction == St.DirectionType.TAB_BACKWARD)
            return super.vfunc_navigate_focus(from, direction);
        return false;
    }
});

function getTermsForSearchString(searchString) {
    searchString = searchString.replace(/^\s+/g, '').replace(/\s+$/g, '');
    if (searchString == '')
        return [];

    let terms = searchString.split(/\s+/);
    return terms;
}

var TouchpadShowOverviewAction = class {
    constructor(actor) {
        actor.connect('captured-event', this._handleEvent.bind(this));
    }

    _handleEvent(actor, event) {
        if (event.type() != Clutter.EventType.TOUCHPAD_PINCH)
            return Clutter.EVENT_PROPAGATE;

        if (event.get_touchpad_gesture_finger_count() != 3)
            return Clutter.EVENT_PROPAGATE;

        if (event.get_gesture_phase() == Clutter.TouchpadGesturePhase.END)
            this.emit('activated', event.get_gesture_pinch_scale ());

        return Clutter.EVENT_STOP;
    }
};
Signals.addSignalMethods(TouchpadShowOverviewAction.prototype);

var ShowOverviewAction = GObject.registerClass({
    Signals: { 'activated': { param_types: [GObject.TYPE_DOUBLE] } },
}, class ShowOverviewAction extends Clutter.GestureAction {
    _init() {
        super._init();
        this.set_n_touch_points(3);

        global.display.connect('grab-op-begin', () => {
            this.cancel();
        });
    }

    vfunc_gesture_prepare(actor) {
        return Main.actionMode == Shell.ActionMode.NORMAL &&
               this.get_n_current_points() == this.get_n_touch_points();
    }

    _getBoundingRect(motion) {
        let minX, minY, maxX, maxY;

        for (let i = 0; i < this.get_n_current_points(); i++) {
            let x, y;

            if (motion == true) {
                [x, y] = this.get_motion_coords(i);
            } else {
                [x, y] = this.get_press_coords(i);
            }

            if (i == 0) {
                minX = maxX = x;
                minY = maxY = y;
            } else {
                minX = Math.min(minX, x);
                minY = Math.min(minY, y);
                maxX = Math.max(maxX, x);
                maxY = Math.max(maxY, y);
            }
        }

        return new Meta.Rectangle({ x: minX,
                                    y: minY,
                                    width: maxX - minX,
                                    height: maxY - minY });
    }

    vfunc_gesture_begin(actor) {
        this._initialRect = this._getBoundingRect(false);
        return true;
    }

    vfunc_gesture_end(actor) {
        let rect = this._getBoundingRect(true);
        let oldArea = this._initialRect.width * this._initialRect.height;
        let newArea = rect.width * rect.height;
        let areaDiff = newArea / oldArea;

        this.emit('activated', areaDiff);
    }
});

var ViewSelector = class {
    constructor(searchEntry, showAppsButton) {
        this.actor = new Shell.Stack({ name: 'viewSelector' });

        this._showAppsButton = showAppsButton;
        this._showAppsButton.connect('notify::checked', this._onShowAppsButtonToggled.bind(this));

        this._activePage = null;

        this._searchActive = false;

        this._entry = searchEntry;
        ShellEntry.addContextMenu(this._entry);

        this._text = this._entry.clutter_text;
        this._text.connect('text-changed', this._onTextChanged.bind(this));
        this._text.connect('key-press-event', this._onKeyPress.bind(this));
        this._text.connect('key-focus-in', () => {
            this._searchResults.highlightDefault(true);
        });
        this._text.connect('key-focus-out', () => {
            this._searchResults.highlightDefault(false);
        });
        this._entry.connect('popup-menu', () => {
            if (!this._searchActive)
                return;

            this._entry.menu.close();
            this._searchResults.popupMenuDefault();
        });
        this._entry.connect('notify::mapped', this._onMapped.bind(this));
        global.stage.connect('notify::key-focus', this._onStageKeyFocusChanged.bind(this));

        this._entry.set_primary_icon(new St.Icon({ style_class: 'search-entry-icon',
                                                   icon_name: 'edit-find-symbolic' }));
        this._clearIcon = new St.Icon({ style_class: 'search-entry-icon',
                                        icon_name: 'edit-clear-symbolic' });

        this._iconClickedId = 0;
        this._capturedEventId = 0;

        this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay();
        this._workspacesPage = this._addPage(this._workspacesDisplay.actor,
                                             _("Windows"), 'focus-windows-symbolic');

        this.appDisplay = new AppDisplay.AppDisplay();
        this._appsPage = this._addPage(this.appDisplay.actor,
                                       _("Applications"), 'view-app-grid-symbolic');

        this._searchResults = new Search.SearchResults();
        this._searchPage = this._addPage(this._searchResults.actor,
                                         _("Search"), 'edit-find-symbolic',
                                         { a11yFocus: this._entry });

        // Since the entry isn't inside the results container we install this
        // dummy widget as the last results container child so that we can
        // include the entry in the keynav tab path
        this._focusTrap = new FocusTrap({ can_focus: true });
        this._focusTrap.connect('key-focus-in', () => {
            this._entry.grab_key_focus();
        });
        this._searchResults.actor.add_actor(this._focusTrap);

        global.focus_manager.add_group(this._searchResults.actor);

        this._stageKeyPressId = 0;
        Main.overview.connect('showing', () => {
            this._stageKeyPressId = global.stage.connect('key-press-event',
                                                         this._onStageKeyPress.bind(this));
        });
        Main.overview.connect('hiding', () => {
            if (this._stageKeyPressId != 0) {
                global.stage.disconnect(this._stageKeyPressId);
                this._stageKeyPressId = 0;
            }
        });
        Main.overview.connect('shown', () => {
            // If we were animating from the desktop view to the
            // apps page the workspace page was visible, allowing
            // the windows to animate, but now we no longer want to
            // show it given that we are now on the apps page or
            // search page.
            if (this._activePage != this._workspacesPage) {
                this._workspacesPage.opacity = 0;
                this._workspacesPage.hide();
            }
        });

        Main.wm.addKeybinding('toggle-application-view',
                              new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                              Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                              Shell.ActionMode.NORMAL |
                              Shell.ActionMode.OVERVIEW,
                              this._toggleAppsPage.bind(this));

        Main.wm.addKeybinding('toggle-overview',
                              new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                              Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                              Shell.ActionMode.NORMAL |
                              Shell.ActionMode.OVERVIEW,
                              Main.overview.toggle.bind(Main.overview));

        let side;
        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
            side = St.Side.RIGHT;
        else
            side = St.Side.LEFT;
        let gesture = new EdgeDragAction.EdgeDragAction(side,
                                                        Shell.ActionMode.NORMAL);
        gesture.connect('activated', () => {
            if (Main.overview.visible)
                Main.overview.hide();
            else
                this.showApps();
        });
        global.stage.add_action(gesture);

        gesture = new ShowOverviewAction();
        gesture.connect('activated', this._pinchGestureActivated.bind(this));
        global.stage.add_action(gesture);

        gesture = new TouchpadShowOverviewAction(global.stage);
        gesture.connect('activated', this._pinchGestureActivated.bind(this));
    }

    _pinchGestureActivated(action, scale) {
        if (scale < PINCH_GESTURE_THRESHOLD)
            Main.overview.show();
    }

    _toggleAppsPage() {
        this._showAppsButton.checked = !this._showAppsButton.checked;
        Main.overview.show();
    }

    showApps() {
        this._showAppsButton.checked = true;
        Main.overview.show();
    }

    show() {
        this.reset();
        this._workspacesDisplay.show(this._showAppsButton.checked);
        this._activePage = null;
        if (this._showAppsButton.checked)
            this._showPage(this._appsPage);
        else
            this._showPage(this._workspacesPage);

        if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
            Main.overview.fadeOutDesktop();
    }

    animateFromOverview() {
        // Make sure workspace page is fully visible to allow
        // workspace.js do the animation of the windows
        this._workspacesPage.opacity = 255;

        this._workspacesDisplay.animateFromOverview(this._activePage != this._workspacesPage);

        this._showAppsButton.checked = false;

        if (!this._workspacesDisplay.activeWorkspaceHasMaximizedWindows())
            Main.overview.fadeInDesktop();
    }

    setWorkspacesFullGeometry(geom) {
        this._workspacesDisplay.setWorkspacesFullGeometry(geom);
    }

    hide() {
        this.reset();
        this._workspacesDisplay.hide();
    }

    _addPage(actor, name, a11yIcon, params) {
        params = Params.parse(params, { a11yFocus: null });

        let page = new St.Bin({
            x_align: St.Align.START,
            y_align: St.Align.START,
            x_fill: true,
            y_fill: true,
        });
        page.set_child(actor);
        if (params.a11yFocus)
            Main.ctrlAltTabManager.addGroup(params.a11yFocus, name, a11yIcon);
        else
            Main.ctrlAltTabManager.addGroup(actor, name, a11yIcon,
                                            { proxy: this.actor,
                                              focusCallback: () => {
                                                  this._a11yFocusPage(page);
                                              }
                                            });;
        page.hide();
        this.actor.add_actor(page);
        return page;
    }

    _fadePageIn() {
        Tweener.addTween(this._activePage,
                         { opacity: 255,
                           time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
                           transition: 'easeOutQuad'
                         });
    }

    _fadePageOut(page) {
        let oldPage = page;
        Tweener.addTween(page,
                         { opacity: 0,
                           time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this._animateIn(oldPage);
                           }
                         });
    }

    _animateIn(oldPage) {
        if (oldPage)
            oldPage.hide();

        this.emit('page-empty');

        this._activePage.show();

        if (this._activePage == this._appsPage && oldPage == this._workspacesPage) {
            // Restore opacity, in case we animated via _fadePageOut
            this._activePage.opacity = 255;
            this.appDisplay.animate(IconGrid.AnimationDirection.IN);
        } else {
            this._fadePageIn();
        }
    }

    _animateOut(page) {
        let oldPage = page;
        if (page == this._appsPage &&
            this._activePage == this._workspacesPage &&
            !Main.overview.animationInProgress) {
            this.appDisplay.animate(IconGrid.AnimationDirection.OUT, () => {
                this._animateIn(oldPage)
            });
        } else {
            this._fadePageOut(page);
        }
    }

    _showPage(page) {
        if (!Main.overview.visible)
            return;

        if (page == this._activePage)
            return;

        let oldPage = this._activePage;
        this._activePage = page;
        this.emit('page-changed');

        if (oldPage)
            this._animateOut(oldPage)
        else
            this._animateIn();
    }

    _a11yFocusPage(page) {
        this._showAppsButton.checked = page == this._appsPage;
        page.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
    }

    _onShowAppsButtonToggled() {
        this._showPage(this._showAppsButton.checked ?
                       this._appsPage : this._workspacesPage);
    }

    _onStageKeyPress(actor, event) {
        // Ignore events while anything but the overview has
        // pushed a modal (system modals, looking glass, ...)
        if (Main.modalCount > 1)
            return Clutter.EVENT_PROPAGATE;

        let modifiers = event.get_state();
        let symbol = event.get_key_symbol();

        if (symbol == Clutter.Escape) {
            if (this._searchActive)
                this.reset();
            else if (this._showAppsButton.checked)
                this._showAppsButton.checked = false;
            else
                Main.overview.hide();
            return Clutter.EVENT_STOP;
        } else if (this._shouldTriggerSearch(symbol)) {
            this.startSearch(event);
        } else if (!this._searchActive && !global.stage.key_focus) {
            if (symbol == Clutter.Tab || symbol == Clutter.Down) {
                this._activePage.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
                return Clutter.EVENT_STOP;
            } else if (symbol == Clutter.ISO_Left_Tab) {
                this._activePage.navigate_focus(null, St.DirectionType.TAB_BACKWARD, false);
                return Clutter.EVENT_STOP;
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _searchCancelled() {
        this._showPage(this._showAppsButton.checked ? this._appsPage
                                                    : this._workspacesPage);

        // Leave the entry focused when it doesn't have any text;
        // when replacing a selected search term, Clutter emits
        // two 'text-changed' signals, one for deleting the previous
        // text and one for the new one - the second one is handled
        // incorrectly when we remove focus
        // (https://bugzilla.gnome.org/show_bug.cgi?id=636341) */
        if (this._text.text != '')
            this.reset();
    }

    reset() {
        // Don't drop the key focus on Clutter's side if anything but the
        // overview has pushed a modal (e.g. system modals when activated using
        // the overview).
        if (Main.modalCount <= 1)
            global.stage.set_key_focus(null);

        this._entry.text = '';

        this._text.set_cursor_visible(true);
        this._text.set_selection(0, 0);
    }

    _onStageKeyFocusChanged() {
        let focus = global.stage.get_key_focus();
        let appearFocused = (this._entry.contains(focus) ||
                             this._searchResults.actor.contains(focus));

        this._text.set_cursor_visible(appearFocused);

        if (appearFocused)
            this._entry.add_style_pseudo_class('focus');
        else
            this._entry.remove_style_pseudo_class('focus');
    }

    _onMapped() {
        if (this._entry.mapped) {
            // Enable 'find-as-you-type'
            this._capturedEventId = global.stage.connect('captured-event',
                                 this._onCapturedEvent.bind(this));
            this._text.set_cursor_visible(true);
            this._text.set_selection(0, 0);
        } else {
            // Disable 'find-as-you-type'
            if (this._capturedEventId > 0)
                global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
    }

    _shouldTriggerSearch(symbol) {
        if (symbol == Clutter.Multi_key)
            return true;

        if (symbol == Clutter.BackSpace && this._searchActive)
            return true;

        let unicode = Clutter.keysym_to_unicode(symbol);
        if (unicode == 0)
            return false;

        if (getTermsForSearchString(String.fromCharCode(unicode)).length > 0)
            return true;

        return false;
    }

    startSearch(event) {
        global.stage.set_key_focus(this._text);

        let synthEvent = event.copy();
        synthEvent.set_source(this._text);
        this._text.event(synthEvent, false);
    }

    // the entry does not show the hint
    _isActivated() {
        return this._text.text == this._entry.get_text();
    }

    _onTextChanged(se, prop) {
        let terms = getTermsForSearchString(this._entry.get_text());

        this._searchActive = (terms.length > 0);
        this._searchResults.setTerms(terms);

        if (this._searchActive) {
            this._showPage(this._searchPage);

            this._entry.set_secondary_icon(this._clearIcon);

            if (this._iconClickedId == 0)
                this._iconClickedId = this._entry.connect('secondary-icon-clicked',
                    this.reset.bind(this));
        } else {
            if (this._iconClickedId > 0) {
                this._entry.disconnect(this._iconClickedId);
                this._iconClickedId = 0;
            }

            this._entry.set_secondary_icon(null);
            this._searchCancelled();
        }
    }

    _onKeyPress(entry, event) {
        let symbol = event.get_key_symbol();
        if (symbol == Clutter.Escape) {
            if (this._isActivated()) {
                this.reset();
                return Clutter.EVENT_STOP;
            }
        } else if (this._searchActive) {
            let arrowNext, nextDirection;
            if (entry.get_text_direction() == Clutter.TextDirection.RTL) {
                arrowNext = Clutter.Left;
                nextDirection = St.DirectionType.LEFT;
            } else {
                arrowNext = Clutter.Right;
                nextDirection = St.DirectionType.RIGHT;
            }

            if (symbol == Clutter.Tab) {
                this._searchResults.navigateFocus(St.DirectionType.TAB_FORWARD);
                return Clutter.EVENT_STOP;
            } else if (symbol == Clutter.ISO_Left_Tab) {
                this._focusTrap.can_focus = false;
                this._searchResults.navigateFocus(St.DirectionType.TAB_BACKWARD);
                this._focusTrap.can_focus = true;
                return Clutter.EVENT_STOP;
            } else if (symbol == Clutter.Down) {
                this._searchResults.navigateFocus(St.DirectionType.DOWN);
                return Clutter.EVENT_STOP;
            } else if (symbol == arrowNext && this._text.position == -1) {
                this._searchResults.navigateFocus(nextDirection);
                return Clutter.EVENT_STOP;
            } else if (symbol == Clutter.Return || symbol == Clutter.KP_Enter) {
                this._searchResults.activateDefault();
                return Clutter.EVENT_STOP;
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onCapturedEvent(actor, event) {
        if (event.type() == Clutter.EventType.BUTTON_PRESS) {
            let source = event.get_source();
            if (source != this._text &&
                this._text.text == '' &&
                !this._text.has_preedit () &&
                !Main.layoutManager.keyboardBox.contains(source)) {
                // the user clicked outside after activating the entry, but
                // with no search term entered and no keyboard button pressed
                // - cancel the search
                this.reset();
            }
        }

        return Clutter.EVENT_PROPAGATE;
    }

    getActivePage() {
        if (this._activePage == this._workspacesPage)
            return ViewPage.WINDOWS;
        else if (this._activePage == this._appsPage)
            return ViewPage.APPS;
        else
            return ViewPage.SEARCH;
    }

    fadeIn() {
        let actor = this._activePage;
        Tweener.addTween(actor, { opacity: 255,
                                  time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME / 2,
                                  transition: 'easeInQuad'
                                });
    }

    fadeHalf() {
        let actor = this._activePage;
        Tweener.addTween(actor, { opacity: 128,
                                  time: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME / 2,
                                  transition: 'easeOutQuad'
                                });
    }
};
Signals.addSignalMethods(ViewSelector.prototype);
(uuay)shell/D};�overviewControls.js�@// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Meta, St } = imports.gi;

const Dash = imports.ui.dash;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;
const ViewSelector = imports.ui.viewSelector;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;

var SIDE_CONTROLS_ANIMATION_TIME = 0.16;

function getRtlSlideDirection(direction, actor) {
    let rtl = (actor.text_direction == Clutter.TextDirection.RTL);
    if (rtl)
        direction = (direction == SlideDirection.LEFT) ?
            SlideDirection.RIGHT : SlideDirection.LEFT;

    return direction;
};

var SlideDirection = {
    LEFT: 0,
    RIGHT: 1
};

var SlideLayout = GObject.registerClass(
class SlideLayout extends Clutter.FixedLayout {
    _init(params) {
        this._slideX = 1;
        this._translationX = undefined;
        this._direction = SlideDirection.LEFT;

        super._init(params);
    }

    vfunc_get_preferred_width(container, forHeight) {
        let child = container.get_first_child();

        let [minWidth, natWidth] = child.get_preferred_width(forHeight);

        minWidth *= this._slideX;
        natWidth *= this._slideX;

        return [minWidth, natWidth];
    }

    vfunc_allocate(container, box, flags) {
        let child = container.get_first_child();

        let availWidth = Math.round(box.x2 - box.x1);
        let availHeight = Math.round(box.y2 - box.y1);
        let [, natWidth] = child.get_preferred_width(availHeight);

        // Align the actor inside the clipped box, as the actor's alignment
        // flags only determine what to do if the allocated box is bigger
        // than the actor's box.
        let realDirection = getRtlSlideDirection(this._direction, child);
        let alignX = (realDirection == SlideDirection.LEFT) ? (availWidth - natWidth)
                                                            : (availWidth - natWidth * this._slideX);

        let actorBox = new Clutter.ActorBox();
        actorBox.x1 = box.x1 + alignX + this._translationX;
        actorBox.x2 = actorBox.x1 + (child.x_expand ? availWidth : natWidth);
        actorBox.y1 = box.y1;
        actorBox.y2 = actorBox.y1 + availHeight;

        child.allocate(actorBox, flags);
    }

    set slideX(value) {
        this._slideX = value;
        this.layout_changed();
    }

    get slideX() {
        return this._slideX;
    }

    set slideDirection(direction) {
        this._direction = direction;
        this.layout_changed();
    }

    get slideDirection() {
        return this._direction;
    }

    set translationX(value) {
        this._translationX = value;
        this.layout_changed();
    }

    get translationX() {
        return this._translationX;
    }
});

var SlidingControl = class {
    constructor(params) {
        params = Params.parse(params, { slideDirection: SlideDirection.LEFT });

        this._visible = true;
        this._inDrag = false;

        this.layout = new SlideLayout();
        this.layout.slideDirection = params.slideDirection;
        this.actor = new St.Widget({ layout_manager: this.layout,
                                     style_class: 'overview-controls',
                                     clip_to_allocation: true });

        Main.overview.connect('hiding', this._onOverviewHiding.bind(this));

        Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
        Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
        Main.overview.connect('item-drag-cancelled', this._onDragEnd.bind(this));

        Main.overview.connect('window-drag-begin', this._onWindowDragBegin.bind(this));
        Main.overview.connect('window-drag-cancelled', this._onWindowDragEnd.bind(this));
        Main.overview.connect('window-drag-end', this._onWindowDragEnd.bind(this));
    }

    _getSlide() {
        throw new Error('getSlide() must be overridden');
    }

    _updateSlide() {
        Tweener.addTween(this.layout, { slideX: this._getSlide(),
                                        time: SIDE_CONTROLS_ANIMATION_TIME,
                                        transition: 'easeOutQuad' });
    }

    getVisibleWidth() {
        let child = this.actor.get_first_child();
        let [, , natWidth, ] = child.get_preferred_size();
        return natWidth;
    }

    _getTranslation() {
        let child = this.actor.get_first_child();
        let direction = getRtlSlideDirection(this.layout.slideDirection, child);
        let visibleWidth = this.getVisibleWidth();

        if (direction == SlideDirection.LEFT)
            return - visibleWidth;
        else
            return visibleWidth;
    }

    _updateTranslation() {
        let translationStart = 0;
        let translationEnd = 0;
        let translation = this._getTranslation();

        let shouldShow = (this._getSlide() > 0);
        if (shouldShow) {
            translationStart = translation;
        } else {
            translationEnd = translation;
        }

        if (this.layout.translationX == translationEnd)
            return;

        this.layout.translationX = translationStart;
        Tweener.addTween(this.layout, { translationX: translationEnd,
                                        time: SIDE_CONTROLS_ANIMATION_TIME,
                                        transition: 'easeOutQuad' });
    }

    _onOverviewHiding() {
        // We need to explicitly slideOut since showing pages
        // doesn't imply sliding out, instead, hiding the overview does.
        this.slideOut();
    }

    _onWindowDragBegin() {
        this._onDragBegin();
    }

    _onWindowDragEnd() {
        this._onDragEnd();
    }

    _onDragBegin() {
        this._inDrag = true;
        this._updateTranslation();
        this._updateSlide();
    }

    _onDragEnd() {
        this._inDrag = false;
        this._updateSlide();
    }

    fadeIn() {
        Tweener.addTween(this.actor, { opacity: 255,
                                       time: SIDE_CONTROLS_ANIMATION_TIME / 2,
                                       transition: 'easeInQuad'
                                     });
    }

    fadeHalf() {
        Tweener.addTween(this.actor, { opacity: 128,
                                       time: SIDE_CONTROLS_ANIMATION_TIME / 2,
                                       transition: 'easeOutQuad'
                                     });
    }

    slideIn() {
        this._visible = true;
        // we will update slideX and the translation from pageEmpty
    }

    slideOut() {
        this._visible = false;
        this._updateTranslation();
        // we will update slideX from pageEmpty
    }

    pageEmpty() {
        // When pageEmpty is received, there's no visible view in the
        // selector; this means we can now safely set the full slide for
        // the next page, since slideIn or slideOut might have been called,
        // changing the visiblity
        this.layout.slideX = this._getSlide();
        this._updateTranslation();
    }
};

var ThumbnailsSlider = class extends SlidingControl {
    constructor(thumbnailsBox) {
        super({ slideDirection: SlideDirection.RIGHT });

        this._thumbnailsBox = thumbnailsBox;

        this.actor.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT;
        this.actor.reactive = true;
        this.actor.track_hover = true;
        this.actor.add_actor(this._thumbnailsBox);

        Main.layoutManager.connect('monitors-changed', this._updateSlide.bind(this));
        global.workspace_manager.connect('active-workspace-changed',
                                         this._updateSlide.bind(this));
        global.workspace_manager.connect('notify::n-workspaces',
                                         this._updateSlide.bind(this));
        this.actor.connect('notify::hover', this._updateSlide.bind(this));
        this._thumbnailsBox.bind_property('visible', this.actor, 'visible', GObject.BindingFlags.SYNC_CREATE);
    }

    _getAlwaysZoomOut() {
        // Always show the pager on hover, during a drag, or if workspaces are
        // actually used, e.g. there are windows on any non-active workspace
        let workspaceManager = global.workspace_manager;
        let alwaysZoomOut = this.actor.hover ||
                            this._inDrag ||
                            !Meta.prefs_get_dynamic_workspaces() ||
                            workspaceManager.n_workspaces > 2 ||
                            workspaceManager.get_active_workspace_index() != 0;

        if (!alwaysZoomOut) {
            let monitors = Main.layoutManager.monitors;
            let primary = Main.layoutManager.primaryMonitor;

            /* Look for any monitor to the right of the primary, if there is
             * one, we always keep zoom out, otherwise its hard to reach
             * the thumbnail area without passing into the next monitor. */
            for (let i = 0; i < monitors.length; i++) {
                if (monitors[i].x >= primary.x + primary.width) {
                    alwaysZoomOut = true;
                    break;
                }
            }
        }

        return alwaysZoomOut;
    }

    getNonExpandedWidth() {
        let child = this.actor.get_first_child();
        return child.get_theme_node().get_length('visible-width');
    }

    _onDragEnd() {
        this.actor.sync_hover();
        super._onDragEnd();
    }

    _getSlide() {
        if (!this._visible)
            return 0;

        let alwaysZoomOut = this._getAlwaysZoomOut();
        if (alwaysZoomOut)
            return 1;

        let child = this.actor.get_first_child();
        let preferredHeight = child.get_preferred_height(-1)[1];
        let expandedWidth = child.get_preferred_width(preferredHeight)[1];

        return this.getNonExpandedWidth() / expandedWidth;
    }

    getVisibleWidth() {
        let alwaysZoomOut = this._getAlwaysZoomOut();
        if (alwaysZoomOut)
            return super.getVisibleWidth();
        else
            return this.getNonExpandedWidth();
    }
};

var DashSlider = class extends SlidingControl {
    constructor(dash) {
        super({ slideDirection: SlideDirection.LEFT });

        this._dash = dash;

        // SlideLayout reads the actor's expand flags to decide
        // whether to allocate the natural size to its child, or the whole
        // available allocation
        this._dash.actor.x_expand = true;

        this.actor.x_expand = true;
        this.actor.x_align = Clutter.ActorAlign.START;
        this.actor.y_expand = true;

        this.actor.add_actor(this._dash.actor);

        this._dash.connect('icon-size-changed', this._updateSlide.bind(this));
    }

    _getSlide() {
        if (this._visible || this._inDrag)
            return 1;
        else
            return 0;
    }

    _onWindowDragBegin() {
        this.fadeHalf();
    }

    _onWindowDragEnd() {
        this.fadeIn();
    }
};

var DashSpacer = GObject.registerClass(
class DashSpacer extends St.Widget {
    _init(params) {
        super._init(params);

        this._bindConstraint = null;
    }

    setDashActor(dashActor) {
        if (this._bindConstraint) {
            this.remove_constraint(this._bindConstraint);
            this._bindConstraint = null;
        }

        if (dashActor) {
            this._bindConstraint = new Clutter.BindConstraint({ source: dashActor,
                                                                coordinate: Clutter.BindCoordinate.SIZE });
            this.add_constraint(this._bindConstraint);
        }
    }

    vfunc_get_preferred_width(forHeight) {
        if (this._bindConstraint)
            return this._bindConstraint.source.get_preferred_width(forHeight);
        return super.vfunc_get_preferred_width(forHeight);
    }

    vfunc_get_preferred_height(forWidth) {
        if (this._bindConstraint)
            return this._bindConstraint.source.get_preferred_height(forWidth);
        return super.vfunc_get_preferred_height(forWidth);
    }
});

var ControlsLayout = GObject.registerClass({
    Signals: { 'allocation-changed': { flags: GObject.SignalFlags.RUN_LAST } },
}, class ControlsLayout extends Clutter.BinLayout {
    vfunc_allocate(container, box, flags) {
        super.vfunc_allocate(container, box, flags);
        this.emit('allocation-changed');
    }
});

var ControlsManager = class {
    constructor(searchEntry) {
        this.dash = new Dash.Dash();
        this._dashSlider = new DashSlider(this.dash);
        this._dashSpacer = new DashSpacer();
        this._dashSpacer.setDashActor(this._dashSlider.actor);

        this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox();
        this._thumbnailsSlider = new ThumbnailsSlider(this._thumbnailsBox);

        this.viewSelector = new ViewSelector.ViewSelector(searchEntry,
                                                          this.dash.showAppsButton);
        this.viewSelector.connect('page-changed', this._setVisibility.bind(this));
        this.viewSelector.connect('page-empty', this._onPageEmpty.bind(this));

        let layout = new ControlsLayout();
        this.actor = new St.Widget({ layout_manager: layout,
                                     x_expand: true, y_expand: true,
                                     clip_to_allocation: true });
        this._group = new St.BoxLayout({ name: 'overview-group',
                                        x_expand: true, y_expand: true });
        this.actor.add_actor(this._group);

        this.actor.add_actor(this._dashSlider.actor);

        this._group.add_actor(this._dashSpacer);
        this._group.add(this.viewSelector.actor, { x_fill: true,
                                                   expand: true });
        this._group.add_actor(this._thumbnailsSlider.actor);

        layout.connect('allocation-changed', this._updateWorkspacesGeometry.bind(this));

        Main.overview.connect('showing', this._updateSpacerVisibility.bind(this));
        Main.overview.connect('item-drag-begin', () => {
            let activePage = this.viewSelector.getActivePage();
            if (activePage != ViewSelector.ViewPage.WINDOWS)
                this.viewSelector.fadeHalf();
        });
        Main.overview.connect('item-drag-end', () => {
            this.viewSelector.fadeIn();
        });
        Main.overview.connect('item-drag-cancelled', () => {
            this.viewSelector.fadeIn();
        });
    }

    _updateWorkspacesGeometry() {
        let [x, y] = this.actor.get_transformed_position();
        let [width, height] = this.actor.get_transformed_size();
        let geometry = { x: x, y: y, width: width, height: height };

        let spacing = this.actor.get_theme_node().get_length('spacing');
        let dashWidth = this._dashSlider.getVisibleWidth() + spacing;
        let thumbnailsWidth = this._thumbnailsSlider.getNonExpandedWidth() + spacing;

        geometry.width -= dashWidth;
        geometry.width -= thumbnailsWidth;

        if (this.actor.get_text_direction() == Clutter.TextDirection.LTR)
            geometry.x += dashWidth;
        else
            geometry.x += thumbnailsWidth;

        this.viewSelector.setWorkspacesFullGeometry(geometry);
    }

    _setVisibility() {
        // Ignore the case when we're leaving the overview, since
        // actors will be made visible again when entering the overview
        // next time, and animating them while doing so is just
        // unnecessary noise
        if (!Main.overview.visible ||
            (Main.overview.animationInProgress && !Main.overview.visibleTarget))
            return;

        let activePage = this.viewSelector.getActivePage();
        let dashVisible = (activePage == ViewSelector.ViewPage.WINDOWS ||
                           activePage == ViewSelector.ViewPage.APPS);
        let thumbnailsVisible = (activePage == ViewSelector.ViewPage.WINDOWS);

        if (dashVisible)
            this._dashSlider.slideIn();
        else
            this._dashSlider.slideOut();

        if (thumbnailsVisible)
            this._thumbnailsSlider.slideIn();
        else
            this._thumbnailsSlider.slideOut();
    }

    _updateSpacerVisibility() {
        if (Main.overview.animationInProgress && !Main.overview.visibleTarget)
            return;

        let activePage = this.viewSelector.getActivePage();
        this._dashSpacer.visible = (activePage == ViewSelector.ViewPage.WINDOWS);
    }

    _onPageEmpty() {
        this._dashSlider.pageEmpty();
        this._thumbnailsSlider.pageEmpty();

        this._updateSpacerVisibility();
    }
};
(uuay)networkAgent.js;// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, NM, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;

const Config = imports.misc.config;
const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;

const VPN_UI_GROUP = 'VPN Plugin UI';

var NetworkSecretDialog = class extends ModalDialog.ModalDialog {
    constructor(agent, requestId, connection, settingName, hints, flags, contentOverride) {
        super({ styleClass: 'prompt-dialog' });

        this._agent = agent;
        this._requestId = requestId;
        this._connection = connection;
        this._settingName = settingName;
        this._hints = hints;

        if (contentOverride)
            this._content = contentOverride;
        else
            this._content = this._getContent();

        let icon = new Gio.ThemedIcon({ name: 'dialog-password-symbolic' });
        let contentParams = { icon,
                              title: this._content.title,
                              body: this._content.message };
        let contentBox = new Dialog.MessageDialogContent(contentParams);
        this.contentLayout.add_actor(contentBox);

        let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
        let secretTable = new St.Widget({ style_class: 'network-dialog-secret-table',
                                          layout_manager: layout });
        layout.hookup_style(secretTable);

        let rtl = secretTable.get_text_direction() == Clutter.TextDirection.RTL;
        let initialFocusSet = false;
        let pos = 0;
        for (let i = 0; i < this._content.secrets.length; i++) {
            let secret = this._content.secrets[i];
            let label = new St.Label({ style_class: 'prompt-dialog-password-label',
                                       text: secret.label,
                                       x_align: Clutter.ActorAlign.START,
                                       y_align: Clutter.ActorAlign.CENTER });
            label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;

            let reactive = secret.key != null;

            secret.entry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
                                          text: secret.value, can_focus: reactive,
                                          reactive: reactive,
                                          x_expand: true });
            ShellEntry.addContextMenu(secret.entry,
                                      { isPassword: secret.password });

            if (secret.validate)
                secret.valid = secret.validate(secret);
            else // no special validation, just ensure it's not empty
                secret.valid = secret.value.length > 0;

            if (reactive) {
                if (!initialFocusSet) {
                    this.setInitialKeyFocus(secret.entry);
                    initialFocusSet = true;
                }

                secret.entry.clutter_text.connect('activate', this._onOk.bind(this));
                secret.entry.clutter_text.connect('text-changed', () => {
                    secret.value = secret.entry.get_text();
                    if (secret.validate)
                        secret.valid = secret.validate(secret);
                    else
                        secret.valid = secret.value.length > 0;
                    this._updateOkButton();
                });
            } else
                secret.valid = true;

            if (rtl) {
                layout.attach(secret.entry, 0, pos, 1, 1);
                layout.attach(label, 1, pos, 1, 1);
            } else {
                layout.attach(label, 0, pos, 1, 1);
                layout.attach(secret.entry, 1, pos, 1, 1);
            }
            pos++;

            if (secret.password)
                secret.entry.clutter_text.set_password_char('\u25cf');
        }

        if (this._content.secrets.some(s => s.password)) {
            this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
            if (rtl)
                layout.attach(this._capsLockWarningLabel, 0, pos, 1, 1);
            else
                layout.attach(this._capsLockWarningLabel, 1, pos, 1, 1);
        }

        contentBox.messageBox.add(secretTable);

        if (flags & NM.SecretAgentGetSecretsFlags.WPS_PBC_ACTIVE) {
            let descriptionLabel = new St.Label({ style_class: 'prompt-dialog-description',
                                                  text: _("Alternatively you can connect by pushing the “WPS” button on your router.") });
            descriptionLabel.clutter_text.line_wrap = true;
            descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;

            contentBox.messageBox.add(descriptionLabel,
                                      { y_fill:  true,
                                        y_align: St.Align.START,
                                        expand: true });
        }

        this._okButton = { label:  _("Connect"),
                           action: this._onOk.bind(this),
                           default: true
                         };

        this.setButtons([{ label: _("Cancel"),
                           action: this.cancel.bind(this),
                           key:    Clutter.KEY_Escape,
                         },
                         this._okButton]);

        this._updateOkButton();
    }

    _updateOkButton() {
        let valid = true;
        for (let i = 0; i < this._content.secrets.length; i++) {
            let secret = this._content.secrets[i];
            valid = valid && secret.valid;
        }

        this._okButton.button.reactive = valid;
        this._okButton.button.can_focus = valid;
    }

    _onOk() {
        let valid = true;
        for (let i = 0; i < this._content.secrets.length; i++) {
            let secret = this._content.secrets[i];
            valid = valid && secret.valid;
            if (secret.key != null)
                this._agent.set_password(this._requestId, secret.key, secret.value);
        }

        if (valid) {
            this._agent.respond(this._requestId, Shell.NetworkAgentResponse.CONFIRMED);
            this.close(global.get_current_time());
        }
        // do nothing if not valid
    }

    cancel() {
        this._agent.respond(this._requestId, Shell.NetworkAgentResponse.USER_CANCELED);
        this.close(global.get_current_time());
    }

    _validateWpaPsk(secret) {
        let value = secret.value;
        if (value.length == 64) {
            // must be composed of hexadecimal digits only
            for (let i = 0; i < 64; i++) {
                if (!((value[i] >= 'a' && value[i] <= 'f')
                      || (value[i] >= 'A' && value[i] <= 'F')
                      || (value[i] >= '0' && value[i] <= '9')))
                    return false;
            }
            return true;
        }

        return (value.length >= 8 && value.length <= 63);
    }

    _validateStaticWep(secret) {
        let value = secret.value;
        if (secret.wep_key_type == NM.WepKeyType.KEY) {
            if (value.length == 10 || value.length == 26) {
		for (let i = 0; i < value.length; i++) {
                    if (!((value[i] >= 'a' && value[i] <= 'f')
                          || (value[i] >= 'A' && value[i] <= 'F')
                          || (value[i] >= '0' && value[i] <= '9')))
                        return false;
		}
	    } else if (value.length == 5 || value.length == 13) {
		for (let i = 0; i < value.length; i++) {
                    if (!((value[i] >= 'a' && value[i] <= 'z')
                          || (value[i] >= 'A' && value[i] <= 'Z')))
                        return false;
                }
            } else
                return false;
	} else if (secret.wep_key_type == NM.WepKeyType.PASSPHRASE) {
	    if (value.length < 0 || value.length > 64)
	        return false;
	}
        return true;
    }

    _getWirelessSecrets(secrets, wirelessSetting) {
        let wirelessSecuritySetting = this._connection.get_setting_wireless_security();

        if (this._settingName == '802-1x') {
            this._get8021xSecrets(secrets);
            return;
        }

        switch (wirelessSecuritySetting.key_mgmt) {
        // First the easy ones
        case 'wpa-none':
        case 'wpa-psk':
        case 'sae':
            secrets.push({ label: _("Password: "), key: 'psk',
                           value: wirelessSecuritySetting.psk || '',
                           validate: this._validateWpaPsk, password: true });
            break;
        case 'none': // static WEP
            secrets.push({ label: _("Key: "), key: 'wep-key' + wirelessSecuritySetting.wep_tx_keyidx,
                           value: wirelessSecuritySetting.get_wep_key(wirelessSecuritySetting.wep_tx_keyidx) || '',
                           wep_key_type: wirelessSecuritySetting.wep_key_type,
                           validate: this._validateStaticWep, password: true });
            break;
        case 'ieee8021x':
            if (wirelessSecuritySetting.auth_alg == 'leap') // Cisco LEAP
                secrets.push({ label: _("Password: "), key: 'leap-password',
                               value: wirelessSecuritySetting.leap_password || '', password: true });
            else // Dynamic (IEEE 802.1x) WEP
                this._get8021xSecrets(secrets);
            break;
        case 'wpa-eap':
            this._get8021xSecrets(secrets);
            break;
        default:
            log('Invalid wireless key management: ' + wirelessSecuritySetting.key_mgmt);
        }
    }

    _get8021xSecrets(secrets) {
        let ieee8021xSetting = this._connection.get_setting_802_1x();
        let phase2method;

        /* If hints were given we know exactly what we need to ask */
        if (this._settingName == "802-1x" && this._hints.length) {
            if (this._hints.includes('identity'))
                secrets.push({ label: _("Username: "), key: 'identity',
                               value: ieee8021xSetting.identity || '', password: false });
            if (this._hints.includes('password'))
                secrets.push({ label: _("Password: "), key: 'password',
                               value: ieee8021xSetting.password || '', password: true });
            if (this._hints.includes('private-key-password'))
                secrets.push({ label: _("Private key password: "), key: 'private-key-password',
                               value: ieee8021xSetting.private_key_password || '', password: true });
            return;
        }

        switch (ieee8021xSetting.get_eap_method(0)) {
        case 'md5':
        case 'leap':
        case 'ttls':
        case 'peap':
        case 'fast':
            // TTLS and PEAP are actually much more complicated, but this complication
            // is not visible here since we only care about phase2 authentication
            // (and don't even care of which one)
            secrets.push({ label: _("Username: "), key: null,
                           value: ieee8021xSetting.identity || '', password: false });
            secrets.push({ label: _("Password: "), key: 'password',
                           value: ieee8021xSetting.password || '', password: true });
            break;
        case 'tls':
            secrets.push({ label: _("Identity: "), key: null,
                           value: ieee8021xSetting.identity || '', password: false });
            secrets.push({ label: _("Private key password: "), key: 'private-key-password',
                           value: ieee8021xSetting.private_key_password || '', password: true });
            break;
        default:
            log('Invalid EAP/IEEE802.1x method: ' + ieee8021xSetting.get_eap_method(0));
        }
    }

    _getPPPoESecrets(secrets) {
        let pppoeSetting = this._connection.get_setting_pppoe();
        secrets.push({ label: _("Username: "), key: 'username',
                       value: pppoeSetting.username || '', password: false });
        secrets.push({ label: _("Service: "), key: 'service',
                       value: pppoeSetting.service || '', password: false });
        secrets.push({ label: _("Password: "), key: 'password',
                       value: pppoeSetting.password || '', password: true });
    }

    _getMobileSecrets(secrets, connectionType) {
        let setting;
        if (connectionType == 'bluetooth')
            setting = this._connection.get_setting_cdma() || this._connection.get_setting_gsm();
        else
            setting = this._connection.get_setting_by_name(connectionType);
        secrets.push({ label: _("Password: "), key: 'password',
                       value: setting.value || '', password: true });
    }

    _getContent() {
        let connectionSetting = this._connection.get_setting_connection();
        let connectionType = connectionSetting.get_connection_type();
        let wirelessSetting;
        let ssid;

        let content = { };
        content.secrets = [ ];

        switch (connectionType) {
        case '802-11-wireless':
            wirelessSetting = this._connection.get_setting_wireless();
            ssid = NM.utils_ssid_to_utf8(wirelessSetting.get_ssid().get_data());
            content.title = _("Authentication required by wireless network");
            content.message = _("Passwords or encryption keys are required to access the wireless network “%s”.").format(ssid);
            this._getWirelessSecrets(content.secrets, wirelessSetting);
            break;
        case '802-3-ethernet':
            content.title = _("Wired 802.1X authentication");
            content.message = null;
            content.secrets.push({ label: _("Network name: "), key: null,
                                   value: connectionSetting.get_id(), password: false });
            this._get8021xSecrets(content.secrets);
            break;
        case 'pppoe':
            content.title = _("DSL authentication");
            content.message = null;
            this._getPPPoESecrets(content.secrets);
            break;
        case 'gsm':
            if (this._hints.indexOf('pin') != -1) {
                let gsmSetting = this._connection.get_setting_gsm();
                content.title = _("PIN code required");
                content.message = _("PIN code is needed for the mobile broadband device");
                content.secrets.push({ label: _("PIN: "), key: 'pin',
                                       value: gsmSetting.pin || '', password: true });
                break;
            }
            // fall through
        case 'cdma':
        case 'bluetooth':
            content.title = _("Mobile broadband network password");
            content.message = _("A password is required to connect to “%s”.").format(connectionSetting.get_id());
            this._getMobileSecrets(content.secrets, connectionType);
            break;
        default:
            log('Invalid connection type: ' + connectionType);
        };

        return content;
    }
};

var VPNRequestHandler = class {
    constructor(agent, requestId, authHelper, serviceType, connection, hints, flags) {
        this._agent = agent;
        this._requestId = requestId;
        this._connection = connection;
        this._flags = flags;
        this._pluginOutBuffer = [];
        this._title = null;
        this._description = null;
        this._content = [ ];
        this._shellDialog = null;

        let connectionSetting = connection.get_setting_connection();

        let argv = [ authHelper.fileName,
                     '-u', connectionSetting.uuid,
                     '-n', connectionSetting.id,
                     '-s', serviceType
                   ];
        if (authHelper.externalUIMode)
            argv.push('--external-ui-mode');
        if (flags & NM.SecretAgentGetSecretsFlags.ALLOW_INTERACTION)
            argv.push('-i');
        if (flags & NM.SecretAgentGetSecretsFlags.REQUEST_NEW)
            argv.push('-r');
        if (authHelper.supportsHints) {
            for (let i = 0; i < hints.length; i++) {
                argv.push('-t');
                argv.push(hints[i]);
            }
        }

        this._newStylePlugin = authHelper.externalUIMode;

        try {
            let [success, pid, stdin, stdout, stderr] =
                GLib.spawn_async_with_pipes(null, /* pwd */
                                            argv,
                                            null, /* envp */
                                            GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                            null /* child_setup */);

            this._childPid = pid;
            this._stdin = new Gio.UnixOutputStream({ fd: stdin, close_fd: true });
            this._stdout = new Gio.UnixInputStream({ fd: stdout, close_fd: true });
            GLib.close(stderr);
            this._dataStdout = new Gio.DataInputStream({ base_stream: this._stdout });

            if (this._newStylePlugin)
                this._readStdoutNewStyle();
            else
                this._readStdoutOldStyle();

            this._childWatch = GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid,
                                                    this._vpnChildFinished.bind(this));

            this._writeConnection();
        } catch(e) {
            logError(e, 'error while spawning VPN auth helper');

            this._agent.respond(requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR);
        }
    }

    cancel(respond) {
        if (respond)
            this._agent.respond(this._requestId, Shell.NetworkAgentResponse.USER_CANCELED);

        if (this._newStylePlugin && this._shellDialog) {
            this._shellDialog.close(global.get_current_time());
            this._shellDialog.destroy();
        } else {
            try {
                this._stdin.write('QUIT\n\n', null);
            } catch(e) { /* ignore broken pipe errors */ }
        }

        this.destroy();
    }

    destroy() {
        if (this._destroyed)
            return;

        this.emit('destroy');
        if (this._childWatch)
            GLib.source_remove(this._childWatch);

        this._stdin.close(null);
        // Stdout is closed when we finish reading from it

        this._destroyed = true;
    }

    _vpnChildFinished(pid, status, requestObj) {
        this._childWatch = 0;
        if (this._newStylePlugin) {
            // For new style plugin, all work is done in the async reading functions
            // Just reap the process here
            return;
        }

        let [exited, exitStatus] = Shell.util_wifexited(status);

        if (exited) {
            if (exitStatus != 0)
                this._agent.respond(this._requestId, Shell.NetworkAgentResponse.USER_CANCELED);
            else
                this._agent.respond(this._requestId, Shell.NetworkAgentResponse.CONFIRMED);
        } else
            this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR);

        this.destroy();
    }

    _vpnChildProcessLineOldStyle(line) {
        if (this._previousLine != undefined) {
            // Two consecutive newlines mean that the child should be closed
            // (the actual newlines are eaten by Gio.DataInputStream)
            // Send a termination message
            if (line == '' && this._previousLine == '') {
                try {
                    this._stdin.write('QUIT\n\n', null);
                } catch(e) { /* ignore broken pipe errors */ }
            } else {
                this._agent.set_password(this._requestId, this._previousLine, line);
                this._previousLine = undefined;
            }
        } else {
            this._previousLine = line;
        }
    }

    _readStdoutOldStyle() {
        this._dataStdout.read_line_async(GLib.PRIORITY_DEFAULT, null, (stream, result) => {
            let [line, len] = this._dataStdout.read_line_finish_utf8(result);

            if (line == null) {
                // end of file
                this._stdout.close(null);
                return;
            }

            this._vpnChildProcessLineOldStyle(line);

            // try to read more!
            this._readStdoutOldStyle();
        });
    }

    _readStdoutNewStyle() {
        this._dataStdout.fill_async(-1, GLib.PRIORITY_DEFAULT, null, (stream, result) => {
            let cnt = this._dataStdout.fill_finish(result);

            if (cnt == 0) {
                // end of file
                this._showNewStyleDialog();

                this._stdout.close(null);
                return;
            }

            // Try to read more
            this._dataStdout.set_buffer_size(2 * this._dataStdout.get_buffer_size());
            this._readStdoutNewStyle();
        });
    }

    _showNewStyleDialog() {
        let keyfile = new GLib.KeyFile();
        let data;
        let contentOverride;

        try {
            data = this._dataStdout.peek_buffer();

            if (data instanceof Uint8Array)
                data = imports.byteArray.toGBytes(data);
            else
                data = data.toGBytes();

            keyfile.load_from_bytes(data, GLib.KeyFileFlags.NONE);

            if (keyfile.get_integer(VPN_UI_GROUP, 'Version') != 2)
                throw new Error('Invalid plugin keyfile version, is %d');

            contentOverride = { title: keyfile.get_string(VPN_UI_GROUP, 'Title'),
                                message: keyfile.get_string(VPN_UI_GROUP, 'Description'),
                                secrets: [] };

            let [groups, len] = keyfile.get_groups();
            for (let i = 0; i < groups.length; i++) {
                if (groups[i] == VPN_UI_GROUP)
                    continue;

                let value = keyfile.get_string(groups[i], 'Value');
                let shouldAsk = keyfile.get_boolean(groups[i], 'ShouldAsk');

                if (shouldAsk) {
                    contentOverride.secrets.push({ label: keyfile.get_string(groups[i], 'Label'),
                                                   key: groups[i],
                                                   value: value,
                                                   password: keyfile.get_boolean(groups[i], 'IsSecret')
                                                 });
                } else {
                    if (!value.length) // Ignore empty secrets
                        continue;

                    this._agent.set_password(this._requestId, groups[i], value);
                }
            }
        } catch(e) {
            // No output is a valid case it means "both secrets are stored"
            if (data.length > 0) {
                logError(e, 'error while reading VPN plugin output keyfile');

                this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR);
                this.destroy();
                return;
            }
        }

        if (contentOverride && contentOverride.secrets.length) {
            // Only show the dialog if we actually have something to ask
            this._shellDialog = new NetworkSecretDialog(this._agent, this._requestId, this._connection, 'vpn', [], this._flags, contentOverride);
            this._shellDialog.open(global.get_current_time());
        } else {
            this._agent.respond(this._requestId, Shell.NetworkAgentResponse.CONFIRMED);
            this.destroy();
        }
    }

    _writeConnection() {
        let vpnSetting = this._connection.get_setting_vpn();

        try {
            vpnSetting.foreach_data_item((key, value) => {
                this._stdin.write('DATA_KEY=' + key + '\n', null);
                this._stdin.write('DATA_VAL=' + (value || '') + '\n\n', null);
            });
            vpnSetting.foreach_secret((key, value) => {
                this._stdin.write('SECRET_KEY=' + key + '\n', null);
                this._stdin.write('SECRET_VAL=' + (value || '') + '\n\n', null);
            });
            this._stdin.write('DONE\n\n', null);
        } catch(e) {
            logError(e, 'internal error while writing connection to helper');

            this._agent.respond(this._requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR);
            this.destroy();
        }
    }
};
Signals.addSignalMethods(VPNRequestHandler.prototype);

var NetworkAgent = class {
    constructor() {
        this._native = new Shell.NetworkAgent({ identifier: 'org.gnome.Shell.NetworkAgent',
                                                capabilities: NM.SecretAgentCapabilities.VPN_HINTS,
                                                auto_register: false
                                              });

        this._dialogs = { };
        this._vpnRequests = { };
        this._notifications = { };

        this._pluginDir = Gio.file_new_for_path(Config.VPNDIR);
        try {
            let monitor = this._pluginDir.monitor(Gio.FileMonitorFlags.NONE, null);
            monitor.connect('changed', () => { this._vpnCacheBuilt = false; });
        } catch(e) {
            log('Failed to create monitor for VPN plugin dir: ' + e.message);
        }

        this._native.connect('new-request', this._newRequest.bind(this));
        this._native.connect('cancel-request', this._cancelRequest.bind(this));

        this._initialized = false;
        this._native.init_async(GLib.PRIORITY_DEFAULT, null, (o, res) => {
            try {
                this._native.init_finish(res);
                this._initialized = true;
            } catch(e) {
                this._native = null;
                logError(e, 'error initializing the NetworkManager Agent');
            }
        });
    }

    enable() {
        if (!this._native)
            return;

        this._native.auto_register = true;
        if (this._initialized && !this._native.registered)
            this._native.register_async(null, null);
    }

    disable() {
        let requestId;

        for (requestId in this._dialogs)
            this._dialogs[requestId].cancel();
        this._dialogs = { };

        for (requestId in this._vpnRequests)
            this._vpnRequests[requestId].cancel(true);
        this._vpnRequests = { };

        for (requestId in this._notifications)
            this._notifications[requestId].destroy();
        this._notifications = { };

        if (!this._native)
            return;

        this._native.auto_register = false;
        if (this._initialized && this._native.registered)
            this._native.unregister_async(null, null);
    }

    _showNotification(requestId, connection, settingName, hints, flags) {
        let source = new MessageTray.Source(_("Network Manager"), 'network-transmit-receive');
        source.policy = new MessageTray.NotificationApplicationPolicy('gnome-network-panel');

        let title, body;

        let connectionSetting = connection.get_setting_connection();
        let connectionType = connectionSetting.get_connection_type();
        switch (connectionType) {
        case '802-11-wireless':
            let wirelessSetting = connection.get_setting_wireless();
            let ssid = NM.utils_ssid_to_utf8(wirelessSetting.get_ssid().get_data());
            title = _("Authentication required by wireless network");
            body = _("Passwords or encryption keys are required to access the wireless network “%s”.").format(ssid);
            break;
        case '802-3-ethernet':
            title = _("Wired 802.1X authentication");
            body = _("A password is required to connect to “%s”.".format(connection.get_id()));
            break;
        case 'pppoe':
            title = _("DSL authentication");
            body = _("A password is required to connect to “%s”.".format(connection.get_id()));
            break;
        case 'gsm':
            if (hints.indexOf('pin') != -1) {
                let gsmSetting = connection.get_setting_gsm();
                title = _("PIN code required");
                body = _("PIN code is needed for the mobile broadband device");
                break;
            }
            // fall through
        case 'cdma':
        case 'bluetooth':
            title = _("Mobile broadband network password");
            body = _("A password is required to connect to “%s”.").format(connectionSetting.get_id());
            break;
        default:
            log('Invalid connection type: ' + connectionType);
            this._native.respond(requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR);
            return;
        }

        let notification = new MessageTray.Notification(source, title, body);

        notification.connect('activated', () => {
            notification.answered = true;
            this._handleRequest(requestId, connection, settingName, hints, flags);
        });

        this._notifications[requestId] = notification;
        notification.connect('destroy', () => {
            if (!notification.answered)
                this._native.respond(requestId, Shell.NetworkAgentResponse.USER_CANCELED);
            delete this._notifications[requestId];
        });

        Main.messageTray.add(source);
        source.notify(notification);
    }

    _newRequest(agent, requestId, connection, settingName, hints, flags) {
        if (!(flags & NM.SecretAgentGetSecretsFlags.USER_REQUESTED))
            this._showNotification(requestId, connection, settingName, hints, flags);
        else
            this._handleRequest(requestId, connection, settingName, hints, flags);
    }

    _handleRequest(requestId, connection, settingName, hints, flags) {
        if (settingName == 'vpn') {
            this._vpnRequest(requestId, connection, hints, flags);
            return;
        }

        let dialog = new NetworkSecretDialog(this._native, requestId, connection, settingName, hints, flags);
        dialog.connect('destroy', () => {
            delete this._dialogs[requestId];
        });
        this._dialogs[requestId] = dialog;
        dialog.open(global.get_current_time());
    }

    _cancelRequest(agent, requestId) {
        if (this._dialogs[requestId]) {
            this._dialogs[requestId].close(global.get_current_time());
            this._dialogs[requestId].destroy();
            delete this._dialogs[requestId];
        } else if (this._vpnRequests[requestId]) {
            this._vpnRequests[requestId].cancel(false);
            delete this._vpnRequests[requestId];
        }
    }

    _vpnRequest(requestId, connection, hints, flags) {
        let vpnSetting = connection.get_setting_vpn();
        let serviceType = vpnSetting.service_type;

        this._buildVPNServiceCache();

        let binary = this._vpnBinaries[serviceType];
        if (!binary) {
            log('Invalid VPN service type (cannot find authentication binary)');

            /* cancel the auth process */
            this._native.respond(requestId, Shell.NetworkAgentResponse.INTERNAL_ERROR);
            return;
        }

        let vpnRequest = new VPNRequestHandler(this._native, requestId, binary, serviceType, connection, hints, flags);
        vpnRequest.connect('destroy', () => {
            delete this._vpnRequests[requestId];
        });
        this._vpnRequests[requestId] = vpnRequest;
    }

    _buildVPNServiceCache() {
        if (this._vpnCacheBuilt)
            return;

        this._vpnCacheBuilt = true;
        this._vpnBinaries = { };

        NM.VpnPluginInfo.list_load().forEach(plugin => {
            let service = plugin.get_service();
            let fileName = plugin.get_auth_dialog();
            let supportsHints = plugin.supports_hints();
            let externalUIMode = false;

            let prop = plugin.lookup_property('GNOME', 'supports-external-ui-mode');
            if (prop) {
                prop = prop.trim().toLowerCase();
                externalUIMode = ['true', 'yes', 'on', '1'].includes(prop);
            }

            if (GLib.file_test(fileName, GLib.FileTest.IS_EXECUTABLE)) {
                let binary = { fileName, externalUIMode, supportsHints };
                this._vpnBinaries[service] = binary;

                plugin.get_aliases().forEach(alias => {
                    this._vpnBinaries[alias] = binary;
                });
            } else {
                log('VPN plugin at %s is not executable'.format(fileName));
            }
        });
    }
};
var Component = NetworkAgent;
(uuay)batch.jsV// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/*
 * Copyright 2011 Red Hat, Inc
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2, or (at your option)
 * any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
 */

/*
 * In order for transformation animations to look good, they need to be
 * incremental and have some order to them (e.g., fade out hidden items,
 * then shrink to close the void left over). Chaining animations in this way can
 * be error-prone and wordy using just Tweener callbacks.
 *
 * The classes in this file help with this:
 *
 * - Task.  encapsulates schedulable work to be run in a specific scope.
 *
 * - ConsecutiveBatch.  runs a series of tasks in order and completes
 *                      when the last in the series finishes.
 *
 * - ConcurrentBatch.  runs a set of tasks at the same time and completes
 *                     when the last to finish completes.
 *
 * - Hold.  prevents a batch from completing the pending task until
 *          the hold is released.
 *
 * The tasks associated with a batch are specified in a list at batch
 * construction time as either task objects or plain functions.
 * Batches are task objects, themselves, so they can be nested.
 *
 * These classes aren't specific to GDM, but were found to be unintuitive and so
 * are not used elsewhere. These APIs may ultimately get dropped entirely and
 * replaced by something else.
 */

const Signals = imports.signals;

var Task = class {
    constructor(scope, handler) {
        if (scope)
            this.scope = scope;
        else
            this.scope = this;

        this.handler = handler;
    }

    run() {
        if (this.handler)
            return this.handler.call(this.scope);

        return null;
    }
};
Signals.addSignalMethods(Task.prototype);

var Hold = class extends Task {
    constructor() {
        super(null, () => this);

        this._acquisitions = 1;
    }

    acquire() {
        if (this._acquisitions <= 0)
            throw new Error("Cannot acquire hold after it's been released");
        this._acquisitions++;
    }

    acquireUntilAfter(hold) {
        if (!hold.isAcquired())
            return;

        this.acquire();
        let signalId = hold.connect('release', () => {
            hold.disconnect(signalId);
            this.release();
        });
    }

    release() {
        this._acquisitions--;

        if (this._acquisitions == 0)
            this.emit('release');
    }

    isAcquired() {
        return this._acquisitions > 0;
    }
};
Signals.addSignalMethods(Hold.prototype);

var Batch = class extends Task {
    constructor(scope, tasks) {
        super();

        this.tasks = [];

        for (let i = 0; i < tasks.length; i++) {
            let task;

            if (tasks[i] instanceof Task) {
                task = tasks[i];
            } else if (typeof tasks[i] == 'function') {
                task = new Task(scope, tasks[i]);
            } else {
                throw new Error('Batch tasks must be functions or Task, Hold or Batch objects');
            }

            this.tasks.push(task);
        }
    }

    process() {
        throw new Error('Not implemented');
    }

    runTask() {
        if (!(this._currentTaskIndex in this.tasks)) {
            return null;
        }

        return this.tasks[this._currentTaskIndex].run();
    }

    _finish() {
        this.hold.release();
    }

    nextTask() {
        this._currentTaskIndex++;

        // if the entire batch of tasks is finished, release
        // the hold and notify anyone waiting on the batch
        if (this._currentTaskIndex >= this.tasks.length) {
            this._finish();
            return;
        }

        this.process();
    }

    _start() {
        // acquire a hold to get released when the entire
        // batch of tasks is finished
        this.hold = new Hold();
        this._currentTaskIndex = 0;
        this.process();
    }

    run() {
        this._start();

        // hold may be destroyed at this point
        // if we're already done running
        return this.hold;
    }

    cancel() {
        this.tasks = this.tasks.splice(0, this._currentTaskIndex + 1);
    }
};
Signals.addSignalMethods(Batch.prototype);

var ConcurrentBatch = class extends Batch {
    process() {
       let hold = this.runTask();

       if (hold) {
           this.hold.acquireUntilAfter(hold);
       }

       // Regardless of the state of the just run task,
       // fire off the next one, so all the tasks can run
       // concurrently.
       this.nextTask();
    }
};
Signals.addSignalMethods(ConcurrentBatch.prototype);

var ConsecutiveBatch = class extends Batch {
    process() {
       let hold = this.runTask();

       if (hold && hold.isAcquired()) {
           // This task is inhibiting the batch. Wait on it
           // before processing the next one.
           let signalId = hold.connect('release', () => {
               hold.disconnect(signalId);
               this.nextTask();
           });
           return;
       } else {
           // This task finished, process the next one
           this.nextTask();
       }
    }
};
Signals.addSignalMethods(ConsecutiveBatch.prototype);
(uuay)edgeDragAction.js�
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GObject, Meta, St } = imports.gi;

const Main = imports.ui.main;

var EDGE_THRESHOLD = 20;
var DRAG_DISTANCE = 80;

var EdgeDragAction = GObject.registerClass({
    Signals: { 'activated': {} },
}, class EdgeDragAction extends Clutter.GestureAction {
    _init(side, allowedModes) {
        super._init();
        this._side = side;
        this._allowedModes = allowedModes;
        this.set_n_touch_points(1);

        global.display.connect('grab-op-begin', () => { this.cancel(); });
    }

    _getMonitorRect(x, y) {
        let rect = new Meta.Rectangle({ x: x - 1, y: y - 1, width: 1, height: 1 });
        let monitorIndex = global.display.get_monitor_index_for_rect(rect);

        return global.display.get_monitor_geometry(monitorIndex);
    }

    vfunc_gesture_prepare(actor) {
        if (this.get_n_current_points() == 0)
            return false;

        if (!(this._allowedModes & Main.actionMode))
            return false;

        let [x, y] = this.get_press_coords(0);
        let monitorRect = this._getMonitorRect(x, y);

        return ((this._side == St.Side.LEFT && x < monitorRect.x + EDGE_THRESHOLD) ||
                (this._side == St.Side.RIGHT && x > monitorRect.x + monitorRect.width - EDGE_THRESHOLD) ||
                (this._side == St.Side.TOP && y < monitorRect.y + EDGE_THRESHOLD) ||
                (this._side == St.Side.BOTTOM && y > monitorRect.y + monitorRect.height - EDGE_THRESHOLD));
    }

    vfunc_gesture_progress(actor) {
        let [startX, startY] = this.get_press_coords(0);
        let [x, y] = this.get_motion_coords(0);
        let offsetX = Math.abs (x - startX);
        let offsetY = Math.abs (y - startY);

        if (offsetX < EDGE_THRESHOLD && offsetY < EDGE_THRESHOLD)
            return true;

        if ((offsetX > offsetY &&
             (this._side == St.Side.TOP || this._side == St.Side.BOTTOM)) ||
            (offsetY > offsetX &&
             (this._side == St.Side.LEFT || this._side == St.Side.RIGHT))) {
            this.cancel();
            return false;
        }

        return true;
    }

    vfunc_gesture_end(actor) {
        let [startX, startY] = this.get_press_coords(0);
        let [x, y] = this.get_motion_coords(0);
        let monitorRect = this._getMonitorRect(startX, startY);

        if ((this._side == St.Side.TOP && y > monitorRect.y + DRAG_DISTANCE) ||
            (this._side == St.Side.BOTTOM && y < monitorRect.y + monitorRect.height - DRAG_DISTANCE) ||
            (this._side == St.Side.LEFT && x > monitorRect.x + DRAG_DISTANCE) ||
            (this._side == St.Side.RIGHT && x < monitorRect.x + monitorRect.width - DRAG_DISTANCE))
            this.emit('activated');
    }
});
(uuay)history.js // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Signals = imports.signals;
const Clutter = imports.gi.Clutter;
const Params = imports.misc.params;

var DEFAULT_LIMIT = 512;

var HistoryManager = class {
    constructor(params) {
        params = Params.parse(params, { gsettingsKey: null,
                                        limit: DEFAULT_LIMIT,
                                        entry: null });

        this._key = params.gsettingsKey;
        this._limit = params.limit;

        this._historyIndex = 0;
        if (this._key) {
            this._history = global.settings.get_strv(this._key);
            global.settings.connect('changed::' + this._key,
                                    this._historyChanged.bind(this));

        } else {
            this._history = [];
        }

        this._entry = params.entry;

        if (this._entry) {
            this._entry.connect('key-press-event', 
                                this._onEntryKeyPress.bind(this));
        }
    }

    _historyChanged() {
        this._history = global.settings.get_strv(this._key);
        this._historyIndex = this._history.length;
    }

    _setPrevItem(text) {
        if (this._historyIndex <= 0)
            return false;

        if (text)
            this._history[this._historyIndex] = text;
        this._historyIndex--;
        this._indexChanged();
        return true;
    }

    _setNextItem(text) {
        if (this._historyIndex >= this._history.length)
            return false;

        if (text)
            this._history[this._historyIndex] = text;
        this._historyIndex++;
        this._indexChanged();
        return true;
    }

    lastItem() {
        if (this._historyIndex != this._history.length) {
            this._historyIndex = this._history.length;
            this._indexChanged();
        }

        return this._historyIndex ? this._history[this._historyIndex -1] : null;
    }

    addItem(input) {
        if (this._history.length == 0 ||
            this._history[this._history.length - 1] != input) {

            this._history = this._history.filter(entry => entry != input);
            this._history.push(input);
            this._save();
        }
        this._historyIndex = this._history.length;
    }

    _onEntryKeyPress(entry, event) {
        let symbol = event.get_key_symbol();
        if (symbol == Clutter.KEY_Up) {
            return this._setPrevItem(entry.get_text());
        } else if (symbol == Clutter.KEY_Down) {
            return this._setNextItem(entry.get_text());
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _indexChanged() {
        let current = this._history[this._historyIndex] || '';
        this.emit('changed', current);

        if (this._entry)
            this._entry.set_text(current);
    }

    _save() {
        if (this._history.length > this._limit)
            this._history.splice(0, this._history.length - this._limit);

        if (this._key)
            global.settings.set_strv(this._key, this._history);
    }
};
Signals.addSignalMethods(HistoryManager.prototype);
(uuay)extensionSystem.jscL// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GLib, Gio, GObject, St } = imports.gi;
const Signals = imports.signals;

const ExtensionDownloader = imports.ui.extensionDownloader;
const ExtensionUtils = imports.misc.extensionUtils;
const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

const { ExtensionState, ExtensionType } = ExtensionUtils;

const ENABLED_EXTENSIONS_KEY = 'enabled-extensions';
const DISABLE_USER_EXTENSIONS_KEY = 'disable-user-extensions';
const EXTENSION_DISABLE_VERSION_CHECK_KEY = 'disable-extension-version-validation';

const UPDATE_CHECK_TIMEOUT = 24 * 60 * 60; // 1 day in seconds

var ExtensionManager = class {
    constructor() {
        this._initted = false;
        this._updateNotified = false;

        this._extensions = new Map();
        this._enabledExtensions = [];
        this._extensionOrder = [];

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
    }

    init() {
        this._installExtensionUpdates();
        this._sessionUpdated();

        GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => {
            ExtensionDownloader.checkForUpdates();
            return GLib.SOURCE_CONTINUE;
        });
        ExtensionDownloader.checkForUpdates();
    }

    lookup(uuid) {
        return this._extensions.get(uuid);
    }

    getUuids() {
        return [...this._extensions.keys()];
    }

    _extensionSupportsSessionMode(uuid) {
        let extension = this.lookup(uuid);

        if (!extension)
            return false;

        if (extension.sessionModes.includes(Main.sessionMode.currentMode))
            return true;

        if (extension.sessionModes.includes(Main.sessionMode.parentMode))
            return true;

        return false;
    }

    _sessionModeCanUseExtension(uuid) {
        if (!Main.sessionMode.allowExtensions)
            return false;

        if (!this._extensionSupportsSessionMode(uuid))
            return false;

        return true;
    }

    _callExtensionDisable(uuid) {
        let extension = this.lookup(uuid);
        if (!extension)
            return;

        if (extension.state != ExtensionState.ENABLED)
            return;

        // "Rebase" the extension order by disabling and then enabling extensions
        // in order to help prevent conflicts.

        // Example:
        //   order = [A, B, C, D, E]
        //   user disables C
        //   this should: disable E, disable D, disable C, enable D, enable E

        let orderIdx = this._extensionOrder.indexOf(uuid);
        let order = this._extensionOrder.slice(orderIdx + 1);
        let orderReversed = order.slice().reverse();

        for (let i = 0; i < orderReversed.length; i++) {
            let uuid = orderReversed[i];
            try {
                this.lookup(uuid).stateObj.disable();
            } catch (e) {
                this.logExtensionError(uuid, e);
            }
        }

        if (extension.stylesheet) {
            let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
            theme.unload_stylesheet(extension.stylesheet);
            delete extension.stylesheet;
        }

        try {
            extension.stateObj.disable();
        } catch(e) {
            this.logExtensionError(uuid, e);
        }

        for (let i = 0; i < order.length; i++) {
            let uuid = order[i];
            try {
                this.lookup(uuid).stateObj.enable();
            } catch (e) {
                this.logExtensionError(uuid, e);
            }
        }

        this._extensionOrder.splice(orderIdx, 1);

        if (extension.state != ExtensionState.ERROR) {
            extension.state = ExtensionState.DISABLED;
            this.emit('extension-state-changed', extension);
        }
    }

    _callExtensionEnable(uuid) {
        if (!this._sessionModeCanUseExtension(uuid))
            return;

        let extension = this.lookup(uuid);
        if (!extension)
            return;

        if (extension.state == ExtensionState.INITIALIZED)
            this._callExtensionInit(uuid);

        if (extension.state != ExtensionState.DISABLED)
            return;

        this._extensionOrder.push(uuid);

        let stylesheetNames = [global.session_mode + '.css', 'stylesheet.css'];
        let theme = St.ThemeContext.get_for_stage(global.stage).get_theme();
        for (let i = 0; i < stylesheetNames.length; i++) {
            try {
                let stylesheetFile = extension.dir.get_child(stylesheetNames[i]);
                theme.load_stylesheet(stylesheetFile);
                extension.stylesheet = stylesheetFile;
                break;
            } catch (e) {
                if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_FOUND))
                    continue; // not an error
                log(`Failed to load stylesheet for extension ${uuid}: ${e.message}`);
                return;
            }
        }

        try {
            extension.stateObj.enable();
            extension.state = ExtensionState.ENABLED;
            this.emit('extension-state-changed', extension);
            return;
        } catch (e) {
            if (extension.stylesheet) {
                theme.unload_stylesheet(extension.stylesheet);
                delete extension.stylesheet;
            }
            this.logExtensionError(uuid, e);
            return;
        }
    }

    enableExtension(uuid) {
        if (!this._extensions.has(uuid))
            return false;

        let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
        if (!enabledExtensions.includes(uuid)) {
            enabledExtensions.push(uuid);
            global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
        }

        return true;
    }

    disableExtension(uuid) {
        if (!this._extensions.has(uuid))
            return false;

        let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
        if (enabledExtensions.includes(uuid)) {
            enabledExtensions = enabledExtensions.filter(item => item !== uuid);
            global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
        }

        return true;
    }

    notifyExtensionUpdate(uuid) {
        let extension = this.lookup(uuid);
        if (!extension)
            return;

        extension.hasUpdate = true;
        this.emit('extension-state-changed', extension);

        if (!this._updateNotified) {
            this._updateNotified = true;

            let source = new ExtensionUpdateSource();
            Main.messageTray.add(source);

            let notification = new MessageTray.Notification(source,
                _('Extension Updates Available'),
                _('Extension updates are ready to be installed.'));
            notification.connect('activated',
                () => source.open());
            source.notify(notification);
        }
    }

    logExtensionError(uuid, error) {
        let extension = this.lookup(uuid);
        if (!extension)
            return;

        let message = '' + error;

        extension.error = message;
        extension.state = ExtensionState.ERROR;
        if (!extension.errors)
            extension.errors = [];
        extension.errors.push(message);

        log('Extension "%s" had error: %s'.format(uuid, message));
        this.emit('extension-state-changed', extension);
    }

    createExtensionObject(uuid, dir, type) {
        let metadataFile = dir.get_child('metadata.json');
        if (!metadataFile.query_exists(null)) {
            throw new Error('Missing metadata.json');
        }

        let metadataContents, success;
        try {
            [success, metadataContents] = metadataFile.load_contents(null);
            if (metadataContents instanceof Uint8Array)
                metadataContents = imports.byteArray.toString(metadataContents);
        } catch (e) {
            throw new Error(`Failed to load metadata.json: ${e}`);
        }
        let meta;
        try {
            meta = JSON.parse(metadataContents);
        } catch (e) {
            throw new Error(`Failed to parse metadata.json: ${e}`);
        }

        let requiredProperties = ['uuid', 'name', 'description', 'shell-version'];
        for (let i = 0; i < requiredProperties.length; i++) {
            let prop = requiredProperties[i];
            if (!meta[prop]) {
                throw new Error(`missing "${prop}" property in metadata.json`);
            }
        }

        if (uuid != meta.uuid) {
            throw new Error(`uuid "${meta.uuid}" from metadata.json does not match directory name "${uuid}"`);
        }

        let extension = {
            metadata: meta,
            uuid: meta.uuid,
            type,
            dir,
            path: dir.get_path(),
            error: '',
            hasPrefs: dir.get_child('prefs.js').query_exists(null),
            hasUpdate: false,
            canChange: false,
            sessionModes: meta['session-modes'] ? meta['session-modes'] : [ 'user' ],
        };
        this._extensions.set(uuid, extension);

        return extension;
    }

    loadExtension(extension) {
        // Default to error, we set success as the last step
        extension.state = ExtensionState.ERROR;

        let checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);

        if (checkVersion && ExtensionUtils.isOutOfDate(extension)) {
            extension.state = ExtensionState.OUT_OF_DATE;
        } else {
            let enabled = this._enabledExtensions.includes(extension.uuid);
            if (enabled) {
                if (!this._callExtensionInit(extension.uuid))
                    return;
                if (extension.state == ExtensionState.DISABLED)
                    this._callExtensionEnable(extension.uuid);
            } else {
                extension.state = ExtensionState.INITIALIZED;
            }
        }

        this._updateCanChange(extension);
        this.emit('extension-state-changed', extension);
    }

    unloadExtension(extension) {
        // Try to disable it -- if it's ERROR'd, we can't guarantee that,
        // but it will be removed on next reboot, and hopefully nothing
        // broke too much.
        this._callExtensionDisable(extension.uuid);

        extension.state = ExtensionState.UNINSTALLED;
        this.emit('extension-state-changed', extension);

        this._extensions.delete(extension.uuid);
        return true;
    }

    reloadExtension(oldExtension) {
        // Grab the things we'll need to pass to createExtensionObject
        // to reload it.
        let { uuid: uuid, dir: dir, type: type } = oldExtension;

        // Then unload the old extension.
        this.unloadExtension(oldExtension);

        // Now, recreate the extension and load it.
        let newExtension;
        try {
            newExtension = this.createExtensionObject(uuid, dir, type);
        } catch (e) {
            this.logExtensionError(uuid, e);
            return;
        }

        this.loadExtension(newExtension);
    }

    _callExtensionInit(uuid) {
        if (!this._sessionModeCanUseExtension(uuid))
            return false;

        let extension = this.lookup(uuid);
        let dir = extension.dir;

        if (!extension)
            throw new Error("Extension was not properly created. Call loadExtension first");

        let extensionJs = dir.get_child('extension.js');
        if (!extensionJs.query_exists(null)) {
            this.logExtensionError(uuid, new Error('Missing extension.js'));
            return false;
        }

        let extensionModule;
        let extensionState = null;

        ExtensionUtils.installImporter(extension);
        try {
            extensionModule = extension.imports.extension;
        } catch(e) {
            this.logExtensionError(uuid, e);
            return false;
        }

        if (extensionModule.init) {
            try {
                extensionState = extensionModule.init(extension);
            } catch (e) {
                this.logExtensionError(uuid, e);
                return false;
            }
        }

        if (!extensionState)
            extensionState = extensionModule;
        extension.stateObj = extensionState;

        extension.state = ExtensionState.DISABLED;
        this.emit('extension-loaded', uuid);
        return true;
    }

    _getModeExtensions() {
        if (Array.isArray(Main.sessionMode.enabledExtensions))
            return Main.sessionMode.enabledExtensions;
        return [];
    }

    _updateCanChange(extension) {
        let hasError =
            extension.state == ExtensionState.ERROR ||
            extension.state == ExtensionState.OUT_OF_DATE;

        let isMode = this._getModeExtensions().includes(extension.uuid);
        let modeOnly = global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY);

        extension.canChange =
            !hasError &&
            global.settings.is_writable(ENABLED_EXTENSIONS_KEY) &&
            (isMode || !modeOnly);
    }

    _getEnabledExtensions() {
        let extensions = this._getModeExtensions();

        if (global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
            return extensions;

        return extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));
    }

    _onUserExtensionsEnabledChanged() {
        this._onEnabledExtensionsChanged();
        this._onSettingsWritableChanged();
    }

    _onEnabledExtensionsChanged() {
        let newEnabledExtensions = this._getEnabledExtensions();

        // Find and enable all the newly enabled extensions: UUIDs found in the
        // new setting, but not in the old one.
        newEnabledExtensions.filter(
            uuid => !this._enabledExtensions.includes(uuid) &&
                    this._extensionSupportsSessionMode(uuid)
        ).forEach(uuid => {
            this._callExtensionEnable(uuid);
        });

        // Find and disable all the newly disabled extensions: UUIDs found in the
        // old setting, but not in the new one, and extensions that don't work with
        // the current session mode.
        this._enabledExtensions.filter(
            item => !newEnabledExtensions.includes(item) ||
                    !this._extensionSupportsSessionMode(item)
        ).forEach(uuid => {
            this._callExtensionDisable(uuid);
        });

        this._enabledExtensions = newEnabledExtensions;
    }

    _onSettingsWritableChanged() {
        for (let extension of this._extensions.values()) {
            this._updateCanChange(extension);
            this.emit('extension-state-changed', extension);
        }
    }

    _onVersionValidationChanged() {
        // we want to reload all extensions, but only enable
        // extensions when allowed by the sessionMode, so
        // temporarily disable them all
        this._enabledExtensions = [];

        // The loop modifies the extensions map, so iterate over a copy
        let extensions = [...this._extensions.values()];
        for (let extension of extensions)
            this.reloadExtension(extension);
        this._enabledExtensions = this._getEnabledExtensions();

        if (Main.sessionMode.allowExtensions) {
            this._enabledExtensions.forEach(uuid => {
                this._callExtensionEnable(uuid);
            });
        }
    }

    _installExtensionUpdates() {
        FileUtils.collectFromDatadirs('extension-updates', true, (dir, info) => {
            let fileType = info.get_file_type();
            if (fileType !== Gio.FileType.DIRECTORY)
                return;
            let uuid = info.get_name();
            let extensionDir = Gio.File.new_for_path(
                GLib.build_filenamev([global.userdatadir, 'extensions', uuid]));

            try {
                FileUtils.recursivelyDeleteDir(extensionDir, false);
                FileUtils.recursivelyMoveDir(dir, extensionDir);
            } catch (e) {
                log('Failed to install extension updates for %s'.format(uuid));
            } finally {
                FileUtils.recursivelyDeleteDir(dir, true);
            }
        });
    }

    _loadExtensions() {
        global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`,
            this._onEnabledExtensionsChanged.bind(this));
        global.settings.connect(`changed::${DISABLE_USER_EXTENSIONS_KEY}`,
            this._onUserExtensionsEnabledChanged.bind(this));
        global.settings.connect(`changed::${EXTENSION_DISABLE_VERSION_CHECK_KEY}`,
            this._onVersionValidationChanged.bind(this));
        global.settings.connect(`writable-changed::${ENABLED_EXTENSIONS_KEY}`,
            this._onSettingsWritableChanged.bind(this));

        this._enabledExtensions = this._getEnabledExtensions();

        let perUserDir = Gio.File.new_for_path(global.userdatadir);
        FileUtils.collectFromDatadirs('extensions', true, (dir, info) => {
            let fileType = info.get_file_type();
            if (fileType != Gio.FileType.DIRECTORY)
                return;
            let uuid = info.get_name();
            let existing = this.lookup(uuid);
            if (existing) {
                log(`Extension ${uuid} already installed in ${existing.path}. ${dir.get_path()} will not be loaded`);
                return;
            }

            let extension;
            let type = dir.has_prefix(perUserDir)
                ? ExtensionType.PER_USER
                : ExtensionType.SYSTEM;
            try {
                extension = this.createExtensionObject(uuid, dir, type);
            } catch (e) {
                logError(e, `Could not load extension ${uuid}`);
                return;
            }
            this.loadExtension(extension);
        });
    }

    _enableAllExtensions() {
        if (!this._initted) {
            this._loadExtensions();
            this._initted = true;
        } else {
            this._enabledExtensions.forEach(uuid => {
                this._callExtensionEnable(uuid);
            });
        }
    }

    _disableAllExtensions() {
        if (this._initted) {
            this._extensionOrder.slice().reverse().forEach(uuid => {
                this._callExtensionDisable(uuid);
            });
        }
    }

    _sessionUpdated() {
        // For now sessionMode.allowExtensions controls extensions from both the
        // 'enabled-extensions' preference and the sessionMode.enabledExtensions
        // property; it might make sense to make enabledExtensions independent
        // from allowExtensions in the future
        if (Main.sessionMode.allowExtensions) {
            // Take care of added or removed sessionMode extensions
            this._onEnabledExtensionsChanged();
            this._enableAllExtensions();
        } else {
            this._disableAllExtensions();
        }
    }
};
Signals.addSignalMethods(ExtensionManager.prototype);

class ExtensionUpdateSource extends MessageTray.Source {
    constructor() {
        const appSys = Shell.AppSystem.get_default();
        this._app = appSys.lookup_app('gnome-shell-extension-prefs.desktop');

        super(this._app.get_name());
    }

    getIcon() {
        return this._app.app_info.get_icon();
    }

    _createPolicy() {
        return new MessageTray.NotificationApplicationPolicy(this._app.id);
    }

    open() {
        this._app.activate();
        Main.overview.hide();
        Main.panel.closeCalendar();
    }
}
(uuay)objectManager.js(// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib } = imports.gi;
const Params = imports.misc.params;
const Signals = imports.signals;

// Specified in the D-Bus specification here:
// http://dbus.freedesktop.org/doc/dbus-specification.html#standard-interfaces-objectmanager
const ObjectManagerIface = `
<node>
<interface name="org.freedesktop.DBus.ObjectManager">
  <method name="GetManagedObjects">
    <arg name="objects" type="a{oa{sa{sv}}}" direction="out"/>
  </method>
  <signal name="InterfacesAdded">
    <arg name="objectPath" type="o"/>
    <arg name="interfaces" type="a{sa{sv}}" />
  </signal>
  <signal name="InterfacesRemoved">
    <arg name="objectPath" type="o"/>
    <arg name="interfaces" type="as" />
  </signal>
</interface>
</node>`;

const ObjectManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(ObjectManagerIface);

var ObjectManager = class {
    constructor(params) {
        params = Params.parse(params, { connection: null,
                                        name: null,
                                        objectPath: null,
                                        knownInterfaces: null,
                                        cancellable: null,
                                        onLoaded: null });

        this._connection = params.connection;
        this._serviceName = params.name;
        this._managerPath = params.objectPath;
        this._cancellable = params.cancellable;

        this._managerProxy = new Gio.DBusProxy({ g_connection: this._connection,
                                                 g_interface_name: ObjectManagerInfo.name,
                                                 g_interface_info: ObjectManagerInfo,
                                                 g_name: this._serviceName,
                                                 g_object_path: this._managerPath,
                                                 g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START });

        this._interfaceInfos = {};
        this._objects = {};
        this._interfaces = {};
        this._onLoaded = params.onLoaded;

        if (params.knownInterfaces)
            this._registerInterfaces(params.knownInterfaces);

        // Start out inhibiting load until at least the proxy
        // manager is loaded and the remote objects are fetched
        this._numLoadInhibitors = 1;
        this._managerProxy.init_async(GLib.PRIORITY_DEFAULT,
                                      this._cancellable,
                                      this._onManagerProxyLoaded.bind(this));
    }

    _tryToCompleteLoad() {
        if (this._numLoadInhibitors == 0)
            return;

        this._numLoadInhibitors--;
        if (this._numLoadInhibitors == 0) {
            if (this._onLoaded)
                this._onLoaded();
        }
    }

    _addInterface(objectPath, interfaceName, onFinished) {
        let info = this._interfaceInfos[interfaceName];

        if (!info) {
           if (onFinished)
               onFinished();
           return;
        }

        let proxy = new Gio.DBusProxy({ g_connection: this._connection,
                                       g_name: this._serviceName,
                                       g_object_path: objectPath,
                                       g_interface_name: interfaceName,
                                       g_interface_info: info,
                                       g_flags: Gio.DBusProxyFlags.DO_NOT_AUTO_START });

        proxy.init_async(GLib.PRIORITY_DEFAULT,
                         this._cancellable,
                         (initable, result) => {
               let error = null;
               try {
                   initable.init_finish(result);
               } catch(e) {
                   logError(e, 'could not initialize proxy for interface ' + interfaceName);

                   if (onFinished)
                       onFinished();
                   return;
               }

               let isNewObject;
               if (!this._objects[objectPath]) {
                   this._objects[objectPath] = {};
                   isNewObject = true;
               } else {
                   isNewObject = false;
               }

               this._objects[objectPath][interfaceName] = proxy;

               if (!this._interfaces[interfaceName])
                   this._interfaces[interfaceName] = [];

               this._interfaces[interfaceName].push(proxy);

               if (isNewObject)
                   this.emit('object-added', objectPath);

               this.emit('interface-added', interfaceName, proxy);

               if (onFinished)
                   onFinished();
        });
    }

    _removeInterface(objectPath, interfaceName) {
        if (!this._objects[objectPath])
            return;

        let proxy = this._objects[objectPath][interfaceName];

        if (this._interfaces[interfaceName]) {
            let index = this._interfaces[interfaceName].indexOf(proxy);

            if (index >= 0)
                this._interfaces[interfaceName].splice(index, 1);

            if (this._interfaces[interfaceName].length == 0)
                delete this._interfaces[interfaceName];
        }

        this.emit('interface-removed', interfaceName, proxy);

        this._objects[objectPath][interfaceName] = null;

        if (Object.keys(this._objects[objectPath]).length == 0) {
            delete this._objects[objectPath];
            this.emit('object-removed', objectPath);
        }
    }

    _onManagerProxyLoaded(initable, result) {
        let error = null;
        try {
            initable.init_finish(result);
        } catch(e) {
            logError(e, 'could not initialize object manager for object ' + this._serviceName);

            this._tryToCompleteLoad();
            return;
        }

        this._managerProxy.connectSignal('InterfacesAdded',
                                         (objectManager, sender, [objectPath, interfaces]) => {
                                             let interfaceNames = Object.keys(interfaces);
                                             for (let i = 0; i < interfaceNames.length; i++)
                                                 this._addInterface(objectPath, interfaceNames[i]);
                                         });
        this._managerProxy.connectSignal('InterfacesRemoved',
                                         (objectManager, sender, [objectPath, interfaceNames]) => {
                                             for (let i = 0; i < interfaceNames.length; i++)
                                                 this._removeInterface(objectPath, interfaceNames[i]);
                                         });

        if (Object.keys(this._interfaceInfos).length == 0) {
            this._tryToCompleteLoad();
            return;
        }

        this._managerProxy.connect('notify::g-name-owner', () => {
            if (this._managerProxy.g_name_owner)
                this._onNameAppeared();
            else
                this._onNameVanished();
        });

        if (this._managerProxy.g_name_owner)
            this._onNameAppeared();
    }

    _onNameAppeared() {
        this._managerProxy.GetManagedObjectsRemote((result, error) => {
            if (!result) {
                if (error) {
                   logError(error, 'could not get remote objects for service ' + this._serviceName + ' path ' + this._managerPath);
                }

                this._tryToCompleteLoad();
                return;
            }

            let [objects] = result;

            if (!objects) {
                this._tryToCompleteLoad();
                return;
            }

            let objectPaths = Object.keys(objects);
            for (let i = 0; i < objectPaths.length; i++) {
                let objectPath = objectPaths[i];
                let object = objects[objectPath];

                let interfaceNames = Object.getOwnPropertyNames(object);
                for (let j = 0; j < interfaceNames.length; j++) {
                    let interfaceName = interfaceNames[j];

                    // Prevent load from completing until the interface is loaded
                    this._numLoadInhibitors++;
                    this._addInterface(objectPath,
                                       interfaceName,
                                       this._tryToCompleteLoad.bind(this));
                }
            }
            this._tryToCompleteLoad();
        });
    }

    _onNameVanished() {
        let objectPaths = Object.keys(this._objects);
        for (let i = 0; i < objectPaths.length; i++) {
            let objectPath = objectPaths[i];
            let object = this._objects[objectPath];

            let interfaceNames = Object.keys(object);
            for (let j = 0; j < interfaceNames.length; j++) {
                let interfaceName = interfaceNames[j];

                if (object[interfaceName])
                    this._removeInterface(objectPath, interfaceName);
            }
        }
    }

    _registerInterfaces(interfaces) {
        for (let i = 0; i < interfaces.length; i++) {
            let info = Gio.DBusInterfaceInfo.new_for_xml(interfaces[i]);
            this._interfaceInfos[info.name] = info;
        }
    }

    getProxy(objectPath, interfaceName) {
        let object = this._objects[objectPath];

        if (!object)
            return null;

        return object[interfaceName];
    }

    getProxiesForInterface(interfaceName) {
        let proxyList = this._interfaces[interfaceName];

        if (!proxyList)
            return [];

        return proxyList;
    }

    getAllProxies() {
        let proxies = [];

        let objectPaths = Object.keys(this._objects);
        for (let i = 0; i < objectPaths.length; i++) {
            let object = this._objects[objectPaths];

            let interfaceNames = Object.keys(object);
            for (let j = 0; j < interfaceNames.length; j++) {
                let interfaceName = interfaceNames[j];
                if (object[interfaceName])
                    proxies.push(object(interfaceName));
            }
        }

        return proxies;
    }
};
Signals.addSignalMethods(ObjectManager.prototype);
(uuay)focusCaretTracker.js9/** -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/*
 * Copyright 2012 Inclusive Design Research Centre, OCAD University.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library. If not, see <http://www.gnu.org/licenses/>.
 *
 * Author:
 *   Joseph Scheuhammer <clown@alum.mit.edu>
 * Contributor:
 *   Magdalen Berns <m.berns@sms.ed.ac.uk>
 */

const Atspi = imports.gi.Atspi;
const Signals = imports.signals;

const CARETMOVED        = 'object:text-caret-moved';
const STATECHANGED      = 'object:state-changed';

var FocusCaretTracker = class FocusCaretTracker {
    constructor() {
        this._atspiListener = Atspi.EventListener.new(this._onChanged.bind(this));

        this._atspiInited = false;
        this._focusListenerRegistered = false;
        this._caretListenerRegistered = false;
    }

    _onChanged(event) {
        if (event.type.indexOf(STATECHANGED) == 0)
            this.emit('focus-changed', event);
        else if (event.type == CARETMOVED)
            this.emit('caret-moved', event);
    }

    _initAtspi() {
        if (!this._atspiInited && Atspi.init() == 0) {
            Atspi.set_timeout(250, 250);
            this._atspiInited = true;
        }

	return this._atspiInited;
    }

    registerFocusListener() {
        if (!this._initAtspi() || this._focusListenerRegistered)
            return;

        this._atspiListener.register(STATECHANGED + ':focused');
        this._atspiListener.register(STATECHANGED + ':selected');
        this._focusListenerRegistered = true;
    }

    registerCaretListener() {
        if (!this._initAtspi() || this._caretListenerRegistered)
            return;

        this._atspiListener.register(CARETMOVED);
        this._caretListenerRegistered = true;
    }

    deregisterFocusListener() {
        if (!this._focusListenerRegistered)
            return;

        this._atspiListener.deregister(STATECHANGED + ':focused');
        this._atspiListener.deregister(STATECHANGED + ':selected');
        this._focusListenerRegistered = false;
    }

    deregisterCaretListener() {
        if (!this._caretListenerRegistered)
            return;

        this._atspiListener.deregister(CARETMOVED);
        this._caretListenerRegistered = false;
    }
};
Signals.addSignalMethods(FocusCaretTracker.prototype);
(uuay)kbdA11yDialog.js�const { Clutter, Gio, GObject } = imports.gi;

const Dialog = imports.ui.dialog;
const ModalDialog = imports.ui.modalDialog;

const KEYBOARD_A11Y_SCHEMA    = 'org.gnome.desktop.a11y.keyboard';
const KEY_STICKY_KEYS_ENABLED = 'stickykeys-enable';
const KEY_SLOW_KEYS_ENABLED   = 'slowkeys-enable';

var KbdA11yDialog = GObject.registerClass(
class KbdA11yDialog extends GObject.Object {
    _init() {
        super._init();

        this._a11ySettings = new Gio.Settings({ schema_id: KEYBOARD_A11Y_SCHEMA });

        let deviceManager = Clutter.DeviceManager.get_default();
        deviceManager.connect('kbd-a11y-flags-changed',
                              this._showKbdA11yDialog.bind(this));
    }

    _showKbdA11yDialog(deviceManager, newFlags, whatChanged) {
        let dialog = new ModalDialog.ModalDialog();
        let title, body;
        let key, enabled;

        if (whatChanged & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) {
            key = KEY_SLOW_KEYS_ENABLED;
            enabled = (newFlags & Clutter.KeyboardA11yFlags.SLOW_KEYS_ENABLED) ? true : false;
            title = enabled ?
                    _("Slow Keys Turned On") :
                    _("Slow Keys Turned Off");
            body = _("You just held down the Shift key for 8 seconds. This is the shortcut " +
                     "for the Slow Keys feature, which affects the way your keyboard works.");

        } else  if (whatChanged & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) {
            key = KEY_STICKY_KEYS_ENABLED;
            enabled = (newFlags & Clutter.KeyboardA11yFlags.STICKY_KEYS_ENABLED) ? true : false;
            title = enabled ?
                    _("Sticky Keys Turned On") :
                    _("Sticky Keys Turned Off");
            body = enabled ?
                   _("You just pressed the Shift key 5 times in a row. This is the shortcut " +
                     "for the Sticky Keys feature, which affects the way your keyboard works.") :
                   _("You just pressed two keys at once, or pressed the Shift key 5 times in a row. " +
                     "This turns off the Sticky Keys feature, which affects the way your keyboard works.");
        } else {
            return;
        }

        let icon = new Gio.ThemedIcon({ name: 'preferences-desktop-accessibility-symbolic' });
        let contentParams = { icon, title, body, styleClass: 'access-dialog' };
        let content = new Dialog.MessageDialogContent(contentParams);

        dialog.contentLayout.add_actor(content);

        dialog.addButton({ label: enabled ? _("Leave On") : _("Turn On"),
                           action: () => {
                               this._a11ySettings.set_boolean(key, true);
                               dialog.close();
                           },
                           default: enabled,
                           key: !enabled ? Clutter.Escape : null });

        dialog.addButton({ label: enabled ? _("Turn Off") : _("Leave Off"),
                           action: () => {
                               this._a11ySettings.set_boolean(key, false);
                               dialog.close();
                           },
                           default: !enabled,
                           key: enabled ? Clutter.Escape : null });

        dialog.open();
    }
});
(uuay)telepathyClient.jsą// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, St } = imports.gi;
const Lang = imports.lang;
const Mainloop = imports.mainloop;

var Tpl = null;
var Tp = null;
try {
    ({ TelepathyGLib: Tp, TelepathyLogger: Tpl } = imports.gi);
} catch(e) {
    log('Telepathy is not available, chat integration will be disabled.');
}

const History = imports.misc.history;
const Main = imports.ui.main;
const MessageList = imports.ui.messageList;
const MessageTray = imports.ui.messageTray;
const Params = imports.misc.params;
const Util = imports.misc.util;

const HAVE_TP = (Tp != null && Tpl != null);

// See Notification.appendMessage
var SCROLLBACK_IMMEDIATE_TIME = 3 * 60; // 3 minutes
var SCROLLBACK_RECENT_TIME = 15 * 60; // 15 minutes
var SCROLLBACK_RECENT_LENGTH = 20;
var SCROLLBACK_IDLE_LENGTH = 5;

// See Source._displayPendingMessages
var SCROLLBACK_HISTORY_LINES = 10;

// See Notification._onEntryChanged
var COMPOSING_STOP_TIMEOUT = 5;

var CHAT_EXPAND_LINES = 12;

var NotificationDirection = {
    SENT: 'chat-sent',
    RECEIVED: 'chat-received'
};

var N_ = s => s;

function makeMessageFromTpMessage(tpMessage, direction) {
    let [text, flags] = tpMessage.to_text();

    let timestamp = tpMessage.get_sent_timestamp();
    if (timestamp == 0)
        timestamp = tpMessage.get_received_timestamp();

    return {
        messageType: tpMessage.get_message_type(),
        text: text,
        sender: tpMessage.sender.alias,
        timestamp: timestamp,
        direction: direction
    };
}


function makeMessageFromTplEvent(event) {
    let sent = event.get_sender().get_entity_type() == Tpl.EntityType.SELF;
    let direction = sent ? NotificationDirection.SENT : NotificationDirection.RECEIVED;

    return {
        messageType: event.get_message_type(),
        text: event.get_message(),
        sender: event.get_sender().get_alias(),
        timestamp: event.get_timestamp(),
        direction: direction
    };
}

var TelepathyComponent = class {
    constructor() {
        this._client = null;

        if (!HAVE_TP)
            return; // Telepathy isn't available

        this._client = new TelepathyClient();
    }

    enable() {
        if (!this._client)
            return;

        try {
            this._client.register();
        } catch (e) {
            throw new Error('Couldn\'t register Telepathy client. Error: \n' + e);
        }

        if (!this._client.account_manager.is_prepared(Tp.AccountManager.get_feature_quark_core()))
            this._client.account_manager.prepare_async(null, null);
    }

    disable() {
        if (!this._client)
            return;

        this._client.unregister();
    }
};

var TelepathyClient = HAVE_TP ? GObject.registerClass(
class TelepathyClient extends Tp.BaseClient {
    _init() {
        // channel path -> ChatSource
        this._chatSources = {};
        this._chatState = Tp.ChannelChatState.ACTIVE;

        // account path -> AccountNotification
        this._accountNotifications = {};

        // Define features we want
        this._accountManager = Tp.AccountManager.dup();
        let factory = this._accountManager.get_factory();
        factory.add_account_features([Tp.Account.get_feature_quark_connection()]);
        factory.add_connection_features([Tp.Connection.get_feature_quark_contact_list()]);
        factory.add_channel_features([Tp.Channel.get_feature_quark_contacts()]);
        factory.add_contact_features([Tp.ContactFeature.ALIAS,
                                      Tp.ContactFeature.AVATAR_DATA,
                                      Tp.ContactFeature.PRESENCE,
                                      Tp.ContactFeature.SUBSCRIPTION_STATES]);

        // Set up a SimpleObserver, which will call _observeChannels whenever a
        // channel matching its filters is detected.
        // The second argument, recover, means _observeChannels will be run
        // for any existing channel as well.
        super._init({ name: 'GnomeShell',
                      account_manager: this._accountManager,
                      uniquify_name: true });

        // We only care about single-user text-based chats
        let filter = {};
        filter[Tp.PROP_CHANNEL_CHANNEL_TYPE] = Tp.IFACE_CHANNEL_TYPE_TEXT;
        filter[Tp.PROP_CHANNEL_TARGET_HANDLE_TYPE] = Tp.HandleType.CONTACT;

        this.set_observer_recover(true);
        this.add_observer_filter(filter);
        this.add_approver_filter(filter);
        this.add_handler_filter(filter);

        // Allow other clients (such as Empathy) to pre-empt our channels if
        // needed
        this.set_delegated_channels_callback(
            this._delegatedChannelsCb.bind(this));
    }

    vfunc_observe_channels(account, conn, channels,
                                     dispatchOp, requests, context) {
        let len = channels.length;
        for (let i = 0; i < len; i++) {
            let channel = channels[i];
            let [targetHandle, targetHandleType] = channel.get_handle();

            if (channel.get_invalidated())
              continue;

            /* Only observe contact text channels */
            if ((!(channel instanceof Tp.TextChannel)) ||
               targetHandleType != Tp.HandleType.CONTACT)
               continue;

            this._createChatSource(account, conn, channel, channel.get_target_contact());
        }

        context.accept();
    }

    _createChatSource(account, conn, channel, contact) {
        if (this._chatSources[channel.get_object_path()])
            return;

        let source = new ChatSource(account, conn, channel, contact, this);

        this._chatSources[channel.get_object_path()] = source;
        source.connect('destroy', () => {
            delete this._chatSources[channel.get_object_path()];
        });
    }

    vfunc_handle_channels(account, conn, channels, requests,
                                    user_action_time, context) {
        this._handlingChannels(account, conn, channels, true);
        context.accept();
    }

    _handlingChannels(account, conn, channels, notify) {
        let len = channels.length;
        for (let i = 0; i < len; i++) {
            let channel = channels[i];

            // We can only handle text channel, so close any other channel
            if (!(channel instanceof Tp.TextChannel)) {
                channel.close_async(null);
                continue;
            }

            if (channel.get_invalidated())
              continue;

            // 'notify' will be true when coming from an actual HandleChannels
            // call, and not when from a successful Claim call. The point is
            // we don't want to notify for a channel we just claimed which
            // has no new messages (for example, a new channel which only has
            // a delivery notification). We rely on _displayPendingMessages()
            // and _messageReceived() to notify for new messages.

            // But we should still notify from HandleChannels because the
            // Telepathy spec states that handlers must foreground channels
            // in HandleChannels calls which are already being handled.

            if (notify && this.is_handling_channel(channel)) {
                // We are already handling the channel, display the source
                let source = this._chatSources[channel.get_object_path()];
                if (source)
                    source.notify();
            }
        }
    }

    vfunc_add_dispatch_operation(account, conn, channels,
                                           dispatchOp, context) {
        let channel = channels[0];
        let chanType = channel.get_channel_type();

        if (channel.get_invalidated()) {
            context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
                                        message: 'Channel is invalidated' }));
            return;
        }

        if (chanType == Tp.IFACE_CHANNEL_TYPE_TEXT)
            this._approveTextChannel(account, conn, channel, dispatchOp, context);
        else
            context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
                                        message: 'Unsupported channel type' }));
    }

    _approveTextChannel(account, conn, channel, dispatchOp, context) {
        let [targetHandle, targetHandleType] = channel.get_handle();

        if (targetHandleType != Tp.HandleType.CONTACT) {
            context.fail(new Tp.Error({ code: Tp.Error.INVALID_ARGUMENT,
                                        message: 'Unsupported handle type' }));
            return;
        }

        // Approve private text channels right away as we are going to handle it
        dispatchOp.claim_with_async(this, (dispatchOp, result) => {
            try {
                dispatchOp.claim_with_finish(result);
                this._handlingChannels(account, conn, [channel], false);
            } catch (err) {
                log('Failed to Claim channel: ' + err);
            }
        });

        context.accept();
    }

    _delegatedChannelsCb(client, channels) {
        // Nothing to do as we don't make a distinction between observed and
        // handled channels.
    }
}) : null;

var ChatSource = class extends MessageTray.Source {
    constructor(account, conn, channel, contact, client) {
        super(contact.get_alias());

        this._account = account;
        this._contact = contact;
        this._client = client;

        this.isChat = true;
        this._pendingMessages = [];

        this._conn = conn;
        this._channel = channel;
        this._closedId = this._channel.connect('invalidated', this._channelClosed.bind(this));

        this._notifyTimeoutId = 0;

        this._presence = contact.get_presence_type();

        this._sentId = this._channel.connect('message-sent', this._messageSent.bind(this));
        this._receivedId = this._channel.connect('message-received', this._messageReceived.bind(this));
        this._pendingId = this._channel.connect('pending-message-removed', this._pendingRemoved.bind(this));

        this._notifyAliasId = this._contact.connect('notify::alias', this._updateAlias.bind(this));
        this._notifyAvatarId = this._contact.connect('notify::avatar-file', this._updateAvatarIcon.bind(this));
        this._presenceChangedId = this._contact.connect('presence-changed', this._presenceChanged.bind(this));

        // Add ourselves as a source.
        Main.messageTray.add(this);

        this._getLogMessages();
    }

    _ensureNotification() {
        if (this._notification)
            return;

        this._notification = new ChatNotification(this);
        this._notification.connect('activated', this.open.bind(this));
        this._notification.connect('updated', () => {
            if (this._banner && this._banner.expanded)
                this._ackMessages();
        });
        this._notification.connect('destroy', () => {
            this._notification = null;
        });
        this.pushNotification(this._notification);
    }

    _createPolicy() {
        if (this._account.protocol_name == 'irc')
            return new MessageTray.NotificationApplicationPolicy('org.gnome.Polari');
        return new MessageTray.NotificationApplicationPolicy('empathy');
    }

    createBanner() {
        this._banner = new ChatNotificationBanner(this._notification);

        // We ack messages when the user expands the new notification
        let id = this._banner.connect('expanded', this._ackMessages.bind(this));
        this._banner.actor.connect('destroy', () => {
            this._banner.disconnect(id);
            this._banner = null;
        });

        return this._banner;
    }

    _updateAlias() {
        let oldAlias = this.title;
        let newAlias = this._contact.get_alias();

        if (oldAlias == newAlias)
            return;

        this.setTitle(newAlias);
        if (this._notification)
            this._notification.appendAliasChange(oldAlias, newAlias);
    }

    getIcon() {
        let file = this._contact.get_avatar_file();
        if (file) {
            return new Gio.FileIcon({ file: file });
        } else {
            return new Gio.ThemedIcon({ name: 'avatar-default' });
        }
    }

    getSecondaryIcon() {
        let iconName;
        let presenceType = this._contact.get_presence_type();

        switch (presenceType) {
            case Tp.ConnectionPresenceType.AVAILABLE:
                iconName = 'user-available';
                break;
            case Tp.ConnectionPresenceType.BUSY:
                iconName = 'user-busy';
                break;
            case Tp.ConnectionPresenceType.OFFLINE:
                iconName = 'user-offline';
                break;
            case Tp.ConnectionPresenceType.HIDDEN:
                iconName = 'user-invisible';
                break;
            case Tp.ConnectionPresenceType.AWAY:
                iconName = 'user-away';
                break;
            case Tp.ConnectionPresenceType.EXTENDED_AWAY:
                iconName = 'user-idle';
                break;
            default:
                iconName = 'user-offline';
       }
       return new Gio.ThemedIcon({ name: iconName });
    }

    _updateAvatarIcon() {
        this.iconUpdated();
        if (this._notifiction)
            this._notification.update(this._notification.title,
                                      this._notification.bannerBodyText,
                                      { gicon: this.getIcon() });
    }

    open() {
        Main.overview.hide();
        Main.panel.closeCalendar();

        if (this._client.is_handling_channel(this._channel)) {
            // We are handling the channel, try to pass it to Empathy or Polari
            // (depending on the channel type)
            // We don't check if either app is availble - mission control will
            // fallback to something else if activation fails

            let target;
            if (this._channel.connection.protocol_name == 'irc')
                target = 'org.freedesktop.Telepathy.Client.Polari';
            else
                target = 'org.freedesktop.Telepathy.Client.Empathy.Chat';
            this._client.delegate_channels_async([this._channel], global.get_current_time(), target, null);
        } else {
            // We are not the handler, just ask to present the channel
            let dbus = Tp.DBusDaemon.dup();
            let cd = Tp.ChannelDispatcher.new(dbus);

            cd.present_channel_async(this._channel, global.get_current_time(), null);
        }
    }

    _getLogMessages() {
        let logManager = Tpl.LogManager.dup_singleton();
        let entity = Tpl.Entity.new_from_tp_contact(this._contact, Tpl.EntityType.CONTACT);

        logManager.get_filtered_events_async(this._account, entity,
                                             Tpl.EventTypeMask.TEXT, SCROLLBACK_HISTORY_LINES,
                                             null, this._displayPendingMessages.bind(this));
    }

    _displayPendingMessages(logManager, result) {
        let [success, events] = logManager.get_filtered_events_finish(result);

        let logMessages = events.map(makeMessageFromTplEvent);
        this._ensureNotification();

        let pendingTpMessages = this._channel.get_pending_messages();
        let pendingMessages = [];

        for (let i = 0; i < pendingTpMessages.length; i++) {
            let message = pendingTpMessages[i];

            if (message.get_message_type() == Tp.ChannelTextMessageType.DELIVERY_REPORT)
                continue;

            pendingMessages.push(makeMessageFromTpMessage(message, NotificationDirection.RECEIVED));

            this._pendingMessages.push(message);
        }

        this.countUpdated();

        let showTimestamp = false;

        for (let i = 0; i < logMessages.length; i++) {
            let logMessage = logMessages[i];
            let isPending = false;

            // Skip any log messages that are also in pendingMessages
            for (let j = 0; j < pendingMessages.length; j++) {
                let pending = pendingMessages[j];
                if (logMessage.timestamp == pending.timestamp && logMessage.text == pending.text) {
                    isPending = true;
                    break;
                }
            }

            if (!isPending) {
                showTimestamp = true;
                this._notification.appendMessage(logMessage, true, ['chat-log-message']);
            }
        }

        if (showTimestamp)
            this._notification.appendTimestamp();

        for (let i = 0; i < pendingMessages.length; i++)
            this._notification.appendMessage(pendingMessages[i], true);

        if (pendingMessages.length > 0)
            this.notify();
    }

    destroy(reason) {
        if (this._client.is_handling_channel(this._channel)) {
            this._ackMessages();
            // The chat box has been destroyed so it can't
            // handle the channel any more.
            this._channel.close_async((channel, result) => {
                channel.close_finish(result);
            });
        } else {
            // Don't indicate any unread messages when the notification
            // that represents them has been destroyed.
            this._pendingMessages = [];
            this.countUpdated();
        }

        // Keep source alive while the channel is open
        if (reason != MessageTray.NotificationDestroyedReason.SOURCE_CLOSED)
            return;

        if (this._destroyed)
            return;

        this._destroyed = true;
        this._channel.disconnect(this._closedId);
        this._channel.disconnect(this._receivedId);
        this._channel.disconnect(this._pendingId);
        this._channel.disconnect(this._sentId);

        this._contact.disconnect(this._notifyAliasId);
        this._contact.disconnect(this._notifyAvatarId);
        this._contact.disconnect(this._presenceChangedId);

        super.destroy(reason);
    }

    _channelClosed() {
        this.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
    }

    /* All messages are new messages for Telepathy sources */
    get count() {
        return this._pendingMessages.length;
    }

    get unseenCount() {
        return this.count;
    }

    get countVisible() {
        return this.count > 0;
    }

    _messageReceived(channel, message) {
        if (message.get_message_type() == Tp.ChannelTextMessageType.DELIVERY_REPORT)
            return;

        this._ensureNotification();
        this._pendingMessages.push(message);
        this.countUpdated();

        message = makeMessageFromTpMessage(message, NotificationDirection.RECEIVED);
        this._notification.appendMessage(message);

        // Wait a bit before notifying for the received message, a handler
        // could ack it in the meantime.
        if (this._notifyTimeoutId != 0)
            Mainloop.source_remove(this._notifyTimeoutId);
        this._notifyTimeoutId = Mainloop.timeout_add(500,
            this._notifyTimeout.bind(this));
        GLib.Source.set_name_by_id(this._notifyTimeoutId, '[gnome-shell] this._notifyTimeout');
    }

    _notifyTimeout() {
        if (this._pendingMessages.length != 0)
            this.notify();

        this._notifyTimeoutId = 0;

        return GLib.SOURCE_REMOVE;
    }

    // This is called for both messages we send from
    // our client and other clients as well.
    _messageSent(channel, message, flags, token) {
        this._ensureNotification();
        message = makeMessageFromTpMessage(message, NotificationDirection.SENT);
        this._notification.appendMessage(message);
    }

    notify() {
        super.notify(this._notification);
    }

    respond(text) {
        let type;
        if (text.slice(0, 4) == '/me ') {
            type = Tp.ChannelTextMessageType.ACTION;
            text = text.slice(4);
        } else {
            type = Tp.ChannelTextMessageType.NORMAL;
        }

        let msg = Tp.ClientMessage.new_text(type, text);
        this._channel.send_message_async(msg, 0, (src, result) => {
            this._channel.send_message_finish(result); 
        });
    }

    setChatState(state) {
        // We don't want to send COMPOSING every time a letter is typed into
        // the entry. We send the state only when it changes. Telepathy/Empathy
        // might change it behind our back if the user is using both
        // gnome-shell's entry and the Empathy conversation window. We could
        // keep track of it with the ChatStateChanged signal but it is good
        // enough right now.
        if (state != this._chatState) {
          this._chatState = state;
          this._channel.set_chat_state_async(state, null);
        }
    }

    _presenceChanged(contact, presence, status, message) {
        if (this._notification)
            this._notification.update(this._notification.title,
                                      this._notification.bannerBodyText,
                                      { secondaryGIcon: this.getSecondaryIcon() });
    }

    _pendingRemoved(channel, message) {
        let idx = this._pendingMessages.indexOf(message);

        if (idx >= 0) {
            this._pendingMessages.splice(idx, 1);
            this.countUpdated();
        }

        if (this._pendingMessages.length == 0 &&
            this._banner && !this._banner.expanded)
            this._banner.hide();
    }

    _ackMessages() {
        // Don't clear our messages here, tp-glib will send a
        // 'pending-message-removed' for each one.
        this._channel.ack_all_pending_messages_async(null);
    }
};

var ChatNotification = class extends MessageTray.Notification {
    constructor(source) {
        super(source, source.title, null,
              { secondaryGIcon: source.getSecondaryIcon() });
        this.setUrgency(MessageTray.Urgency.HIGH);
        this.setResident(true);

        this.messages = [];
        this._timestampTimeoutId = 0;
    }

    destroy(reason) {
        if (this._timestampTimeoutId)
            Mainloop.source_remove(this._timestampTimeoutId);
        this._timestampTimeoutId = 0;
        super.destroy(reason);
    }

    /**
     * appendMessage:
     * @message: An object with the properties:
     *   text: the body of the message,
     *   messageType: a #Tp.ChannelTextMessageType,
     *   sender: the name of the sender,
     *   timestamp: the time the message was sent
     *   direction: a #NotificationDirection
     * 
     * @noTimestamp: Whether to add a timestamp. If %true, no timestamp
     *   will be added, regardless of the difference since the
     *   last timestamp
     */
    appendMessage(message, noTimestamp) {
        let messageBody = GLib.markup_escape_text(message.text, -1);
        let styles = [message.direction];

        if (message.messageType == Tp.ChannelTextMessageType.ACTION) {
            let senderAlias = GLib.markup_escape_text(message.sender, -1);
            messageBody = '<i>%s</i> %s'.format(senderAlias, messageBody);
            styles.push('chat-action');
        }

        if (message.direction == NotificationDirection.RECEIVED)
            this.update(this.source.title, messageBody,
                        { datetime: GLib.DateTime.new_from_unix_local (message.timestamp),
                          bannerMarkup: true });

        let group = (message.direction == NotificationDirection.RECEIVED ?
                     'received' : 'sent');

        this._append({ body: messageBody,
                       group: group,
                       styles: styles,
                       timestamp: message.timestamp,
                       noTimestamp: noTimestamp });
    }

    _filterMessages() {
        if (this.messages.length < 1)
            return;

        let lastMessageTime = this.messages[0].timestamp;
        let currentTime = (Date.now() / 1000);

        // Keep the scrollback from growing too long. If the most
        // recent message (before the one we just added) is within
        // SCROLLBACK_RECENT_TIME, we will keep
        // SCROLLBACK_RECENT_LENGTH previous messages. Otherwise
        // we'll keep SCROLLBACK_IDLE_LENGTH messages.

        let maxLength = (lastMessageTime < currentTime - SCROLLBACK_RECENT_TIME) ?
            SCROLLBACK_IDLE_LENGTH : SCROLLBACK_RECENT_LENGTH;

        let filteredHistory = this.messages.filter(item => item.realMessage);
        if (filteredHistory.length > maxLength) {
            let lastMessageToKeep = filteredHistory[maxLength];
            let expired = this.messages.splice(this.messages.indexOf(lastMessageToKeep));
            for (let i = 0; i < expired.length; i++)
                this.emit('message-removed', expired[i]);
        }
    }

    /**
     * _append:
     * @props: An object with the properties:
     *  body: The text of the message.
     *  group: The group of the message, one of:
     *         'received', 'sent', 'meta'.
     *  styles: Style class names for the message to have.
     *  timestamp: The timestamp of the message.
     *  noTimestamp: suppress timestamp signal?
     */
    _append(props) {
        let currentTime = (Date.now() / 1000);
        props = Params.parse(props, { body: null,
                                      group: null,
                                      styles: [],
                                      timestamp: currentTime,
                                      noTimestamp: false });

        // Reset the old message timeout
        if (this._timestampTimeoutId)
            Mainloop.source_remove(this._timestampTimeoutId);
        this._timestampTimeoutId = 0;

        let message = { realMessage: props.group != 'meta',
                        showTimestamp: false };
        Lang.copyProperties(props, message);
        delete message.noTimestamp;

        this.messages.unshift(message);
        this.emit('message-added', message);

        if (!props.noTimestamp) {
            let timestamp = props.timestamp;
            if (timestamp < currentTime - SCROLLBACK_IMMEDIATE_TIME) {
                this.appendTimestamp();
            } else {
                // Schedule a new timestamp in SCROLLBACK_IMMEDIATE_TIME
                // from the timestamp of the message.
                this._timestampTimeoutId = Mainloop.timeout_add_seconds(
                    SCROLLBACK_IMMEDIATE_TIME - (currentTime - timestamp),
                    this.appendTimestamp.bind(this));
                GLib.Source.set_name_by_id(this._timestampTimeoutId, '[gnome-shell] this.appendTimestamp');
            }
        }

        this._filterMessages();
    }

    appendTimestamp() {
        this._timestampTimeoutId = 0;

        this.messages[0].showTimestamp = true;
        this.emit('timestamp-changed', this.messages[0]);

        this._filterMessages();

        return GLib.SOURCE_REMOVE;
    }

    appendAliasChange(oldAlias, newAlias) {
        oldAlias = GLib.markup_escape_text(oldAlias, -1);
        newAlias = GLib.markup_escape_text(newAlias, -1);

        /* Translators: this is the other person changing their old IM name to their new
           IM name. */
        let message = '<i>' + _("%s is now known as %s").format(oldAlias, newAlias) + '</i>';

        this._append({ body: message,
                       group: 'meta',
                       styles: ['chat-meta-message'] });

        this._filterMessages();
    }
};

var ChatLineBox = GObject.registerClass(
class ChatLineBox extends St.BoxLayout {
    vfunc_get_preferred_height(forWidth) {
        let [, natHeight] = super.vfunc_get_preferred_height(forWidth);
        return [natHeight, natHeight];
    }
});

var ChatNotificationBanner = class extends MessageTray.NotificationBanner {
    constructor(notification) {
        super(notification);

        this._responseEntry = new St.Entry({ style_class: 'chat-response',
                                             x_expand: true,
                                             can_focus: true });
        this._responseEntry.clutter_text.connect('activate', this._onEntryActivated.bind(this));
        this._responseEntry.clutter_text.connect('text-changed', this._onEntryChanged.bind(this));
        this.setActionArea(this._responseEntry);

        this._responseEntry.clutter_text.connect('key-focus-in', () => {
            this.focused = true;
        });
        this._responseEntry.clutter_text.connect('key-focus-out', () => {
            this.focused = false;
            this.emit('unfocused');
        });

        this._scrollArea = new St.ScrollView({ style_class: 'chat-scrollview vfade',
                                               vscrollbar_policy: St.PolicyType.AUTOMATIC,
                                               hscrollbar_policy: St.PolicyType.NEVER,
                                               visible: this.expanded });
        this._contentArea = new St.BoxLayout({ style_class: 'chat-body',
                                               vertical: true });
        this._scrollArea.add_actor(this._contentArea);

        this.setExpandedBody(this._scrollArea);
        this.setExpandedLines(CHAT_EXPAND_LINES);

        this._lastGroup = null;

        // Keep track of the bottom position for the current adjustment and
        // force a scroll to the bottom if things change while we were at the
        // bottom
        this._oldMaxScrollValue = this._scrollArea.vscroll.adjustment.value;
        this._scrollArea.vscroll.adjustment.connect('changed', adjustment => {
            if (adjustment.value == this._oldMaxScrollValue)
                this.scrollTo(St.Side.BOTTOM);
            this._oldMaxScrollValue = Math.max(adjustment.lower, adjustment.upper - adjustment.page_size);
        });

        this._inputHistory = new History.HistoryManager({ entry: this._responseEntry.clutter_text });

        this._composingTimeoutId = 0;

        this._messageActors = new Map();

        this._messageAddedId = this.notification.connect('message-added',
            (n, message) => {
                this._addMessage(message);
            });
        this._messageRemovedId = this.notification.connect('message-removed',
            (n, message) => {
                let actor = this._messageActors.get(message);
                if (this._messageActors.delete(message))
                    actor.destroy();
            });
        this._timestampChangedId = this.notification.connect('timestamp-changed',
            (n, message) => {
                this._updateTimestamp(message);
            });

        for (let i = this.notification.messages.length - 1; i >= 0; i--)
            this._addMessage(this.notification.messages[i]);
    }

    _onDestroy() {
        super._onDestroy();
        this.notification.disconnect(this._messageAddedId);
        this.notification.disconnect(this._messageRemovedId);
        this.notification.disconnect(this._timestampChangedId);
    }

    scrollTo(side) {
        let adjustment = this._scrollArea.vscroll.adjustment;
        if (side == St.Side.TOP)
            adjustment.value = adjustment.lower;
        else if (side == St.Side.BOTTOM)
            adjustment.value = adjustment.upper;
    }

    hide() {
        this.emit('done-displaying');
    }

    _addMessage(message) {
        let highlighter = new MessageList.URLHighlighter(message.body, true, true);
        let body = highlighter.actor;

        let styles = message.styles;
        for (let i = 0; i < styles.length; i++)
            body.add_style_class_name(styles[i]);

        let group = message.group;
        if (group != this._lastGroup) {
            this._lastGroup = group;
            body.add_style_class_name('chat-new-group');
        }

        let lineBox = new ChatLineBox();
        lineBox.add(body);
        this._contentArea.add_actor(lineBox);
        this._messageActors.set(message, lineBox);

        this._updateTimestamp(message);
    }

    _updateTimestamp(message) {
        let actor = this._messageActors.get(message);
        if (!actor)
            return;

        while (actor.get_n_children() > 1)
            actor.get_child_at_index(1).destroy();

        if (message.showTimestamp) {
            let lastMessageTime = message.timestamp;
            let lastMessageDate = new Date(lastMessageTime * 1000);

            let timeLabel = Util.createTimeLabel(lastMessageDate);
            timeLabel.style_class = 'chat-meta-message';
            timeLabel.x_expand = timeLabel.y_expand = true;
            timeLabel.x_align = timeLabel.y_align = Clutter.ActorAlign.END;

            actor.add_actor(timeLabel);
        }
    }

    _onEntryActivated() {
        let text = this._responseEntry.get_text();
        if (text == '')
            return;

        this._inputHistory.addItem(text);

        // Telepathy sends out the Sent signal for us.
        // see Source._messageSent
        this._responseEntry.set_text('');
        this.notification.source.respond(text);
    }

    _composingStopTimeout() {
        this._composingTimeoutId = 0;

        this.notification.source.setChatState(Tp.ChannelChatState.PAUSED);

        return GLib.SOURCE_REMOVE;
    }

    _onEntryChanged() {
        let text = this._responseEntry.get_text();

        // If we're typing, we want to send COMPOSING.
        // If we empty the entry, we want to send ACTIVE.
        // If we've stopped typing for COMPOSING_STOP_TIMEOUT
        //    seconds, we want to send PAUSED.

        // Remove composing timeout.
        if (this._composingTimeoutId > 0) {
            Mainloop.source_remove(this._composingTimeoutId);
            this._composingTimeoutId = 0;
        }

        if (text != '') {
            this.notification.source.setChatState(Tp.ChannelChatState.COMPOSING);

            this._composingTimeoutId = Mainloop.timeout_add_seconds(
                COMPOSING_STOP_TIMEOUT,
                this._composingStopTimeout.bind(this));
            GLib.Source.set_name_by_id(this._composingTimeoutId, '[gnome-shell] this._composingStopTimeout');
        } else {
            this.notification.source.setChatState(Tp.ChannelChatState.ACTIVE);
        }
    }
};

var Component = TelepathyComponent;
(uuay)workspace.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, GLib, GObject, Meta, Pango, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const DND = imports.ui.dnd;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const Tweener = imports.ui.tweener;

var WINDOW_DND_SIZE = 256;

var WINDOW_CLONE_MAXIMUM_SCALE = 1.0;

var WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
var WINDOW_OVERLAY_FADE_TIME = 0.1;

var WINDOW_REPOSITIONING_DELAY = 750;

var DRAGGING_WINDOW_OPACITY = 100;

// When calculating a layout, we calculate the scale of windows and the percent
// of the available area the new layout uses. If the values for the new layout,
// when weighted with the values as below, are worse than the previous layout's,
// we stop looking for a new layout and use the previous layout.
// Otherwise, we keep looking for a new layout.
var LAYOUT_SCALE_WEIGHT = 1;
var LAYOUT_SPACE_WEIGHT = 0.1;

var WINDOW_ANIMATION_MAX_NUMBER_BLENDING = 3;

function _interpolate(start, end, step) {
    return start + (end - start) * step;
}

var WindowCloneLayout = GObject.registerClass(
class WindowCloneLayout extends Clutter.LayoutManager {
    _init(boundingBox) {
        super._init();

        this._boundingBox = boundingBox;
    }

    get boundingBox() {
        return this._boundingBox;
    }

    set boundingBox(b) {
        this._boundingBox = b;
        this.layout_changed();
    }

    _makeBoxForWindow(window) {
        // We need to adjust the position of the actor because of the
        // consequences of invisible borders -- in reality, the texture
        // has an extra set of "padding" around it that we need to trim
        // down.

        // The outer rect (from which we compute the bounding box)
        // paradoxically is the smaller rectangle, containing the positions
        // of the visible frame. The input rect contains everything,
        // including the invisible border padding.
        let inputRect = window.get_buffer_rect();

        let box = new Clutter.ActorBox();

        box.set_origin(inputRect.x - this._boundingBox.x,
                       inputRect.y - this._boundingBox.y);
        box.set_size(inputRect.width, inputRect.height);

        return box;
    }

    vfunc_get_preferred_height(container, forWidth) {
        return [this._boundingBox.height, this._boundingBox.height];
    }

    vfunc_get_preferred_width(container, forHeight) {
        return [this._boundingBox.width, this._boundingBox.width];
    }

    vfunc_allocate(container, box, flags) {
        container.get_children().forEach(child => {
            let realWindow;
            if (child == container._delegate._windowClone)
                realWindow = container._delegate.realWindow;
            else
                realWindow = child.source;

            child.allocate(this._makeBoxForWindow(realWindow.meta_window),
                           flags);
        });
    }
});

var WindowClone = class {
    constructor(realWindow, workspace) {
        this.realWindow = realWindow;
        this.metaWindow = realWindow.meta_window;
        this.metaWindow._delegate = this;
        this._workspace = workspace;

        this._windowClone = new Clutter.Clone({ source: realWindow });
        // We expect this.actor to be used for all interaction rather than
        // this._windowClone; as the former is reactive and the latter
        // is not, this just works for most cases. However, for DND all
        // actors are picked, so DND operations would operate on the clone.
        // To avoid this, we hide it from pick.
        Shell.util_set_hidden_from_pick(this._windowClone, true);

        // The MetaShapedTexture that we clone has a size that includes
        // the invisible border; this is inconvenient; rather than trying
        // to compensate all over the place we insert a ClutterActor into
        // the hierarchy that is sized to only the visible portion.
        this.actor = new St.Widget({ reactive: true,
                                     can_focus: true,
                                     accessible_role: Atk.Role.PUSH_BUTTON,
                                     layout_manager: new WindowCloneLayout() });

        this.actor.add_child(this._windowClone);

        this.actor._delegate = this;

        this.slotId = 0;
        this._slot = [0, 0, 0, 0];
        this._dragSlot = [0, 0, 0, 0];
        this._stackAbove = null;

        this._windowClone._sizeChangedId = this.metaWindow.connect('size-changed',
            this._onMetaWindowSizeChanged.bind(this));
        this._windowClone._posChangedId = this.metaWindow.connect('position-changed',
            this._computeBoundingBox.bind(this));
        this._windowClone._destroyId =
            this.realWindow.connect('destroy', () => {
                // First destroy the clone and then destroy everything
                // This will ensure that we never see it in the
                // _disconnectSignals loop
                this._windowClone.destroy();
                this.destroy();
            });

        this._updateAttachedDialogs();
        this._computeBoundingBox();
        this.actor.x = this._boundingBox.x;
        this.actor.y = this._boundingBox.y;

        let clickAction = new Clutter.ClickAction();
        clickAction.connect('clicked', this._onClicked.bind(this));
        clickAction.connect('long-press', this._onLongPress.bind(this));
        this.actor.add_action(clickAction);
        this.actor.connect('destroy', this._onDestroy.bind(this));
        this.actor.connect('key-press-event', this._onKeyPress.bind(this));

        this.actor.connect('enter-event', () => { this.emit('show-chrome'); });
        this.actor.connect('key-focus-in', () => { this.emit('show-chrome'); });

        this.actor.connect('leave-event', () => { this.emit('hide-chrome'); });
        this.actor.connect('key-focus-out', () => { this.emit('hide-chrome'); });

        this._draggable = DND.makeDraggable(this.actor,
                                            { restoreOnSuccess: true,
                                              manualMode: true,
                                              dragActorMaxSize: WINDOW_DND_SIZE,
                                              dragActorOpacity: DRAGGING_WINDOW_OPACITY });
        this._draggable.connect('drag-begin', this._onDragBegin.bind(this));
        this._draggable.connect('drag-cancelled', this._onDragCancelled.bind(this));
        this._draggable.connect('drag-end', this._onDragEnd.bind(this));
        this.inDrag = false;

        this._selected = false;
        this._closeRequested = false;
    }

    set slot(slot) {
        this._slot = slot;
    }

    get slot() {
        if (this.inDrag)
            return this._dragSlot;
        else
            return this._slot;
    }

    deleteAll() {
        // Delete all windows, starting from the bottom-most (most-modal) one
        let windows = this.actor.get_children();
        for (let i = windows.length - 1; i >= 1; i--) {
            let realWindow = windows[i].source;
            let metaWindow = realWindow.meta_window;

            metaWindow.delete(global.get_current_time());
        }

        this.metaWindow.delete(global.get_current_time());
        this._closeRequested = true;
    }

    addDialog(win) {
        let parent = win.get_transient_for();
        while (parent.is_attached_dialog())
            parent = parent.get_transient_for();

        // Display dialog if it is attached to our metaWindow
        if (win.is_attached_dialog() && parent == this.metaWindow) {
            this._doAddAttachedDialog(win, win.get_compositor_private());
            this._onMetaWindowSizeChanged();
        }

        // The dialog popped up after the user tried to close the window,
        // assume it's a close confirmation and leave the overview
        if (this._closeRequested)
            this._activate();
    }

    hasAttachedDialogs() {
        return this.actor.get_n_children() > 1;
    }

    _doAddAttachedDialog(metaWin, realWin) {
        let clone = new Clutter.Clone({ source: realWin });
        clone._sizeChangedId = metaWin.connect('size-changed',
            this._onMetaWindowSizeChanged.bind(this));
        clone._posChangedId = metaWin.connect('position-changed',
            this._onMetaWindowSizeChanged.bind(this));
        clone._destroyId = realWin.connect('destroy', () => {
            clone.destroy();

            this._onMetaWindowSizeChanged();
        });
        this.actor.add_child(clone);
    }

    _updateAttachedDialogs() {
        let iter = win => {
            let actor = win.get_compositor_private();

            if (!actor)
                return false;
            if (!win.is_attached_dialog())
                return false;

            this._doAddAttachedDialog(win, actor);
            win.foreach_transient(iter);
            return true;
        };
        this.metaWindow.foreach_transient(iter);
    }

    get boundingBox() {
        return this._boundingBox;
    }

    get width() {
        return this._boundingBox.width;
    }

    get height() {
        return this._boundingBox.height;
    }

    getOriginalPosition() {
        return [this._boundingBox.x, this._boundingBox.y];
    }

    _computeBoundingBox() {
        let rect = this.metaWindow.get_frame_rect();

        this.actor.get_children().forEach(child => {
            let realWindow;
            if (child == this._windowClone)
                realWindow = this.realWindow;
            else
                realWindow = child.source;

            let metaWindow = realWindow.meta_window;
            rect = rect.union(metaWindow.get_frame_rect());
        });

        // Convert from a MetaRectangle to a native JS object
        this._boundingBox = { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
        this.actor.layout_manager.boundingBox = rect;
    }

    // Find the actor just below us, respecting reparenting done by DND code
    getActualStackAbove() {
        if (this._stackAbove == null)
            return null;

        if (this.inDrag) {
            if (this._stackAbove._delegate)
                return this._stackAbove._delegate.getActualStackAbove();
            else
                return null;
        } else {
            return this._stackAbove;
        }
    }

    setStackAbove(actor) {
        this._stackAbove = actor;
        if (this.inDrag)
            // We'll fix up the stack after the drag
            return;

        let actualAbove = this.getActualStackAbove();
        if (actualAbove == null)
            this.actor.lower_bottom();
        else
            this.actor.raise(actualAbove);
    }

    destroy() {
        this.actor.destroy();
    }

    _disconnectSignals() {
        this.actor.get_children().forEach(child => {
            let realWindow;
            if (child == this._windowClone)
                realWindow = this.realWindow;
            else
                realWindow = child.source;

            realWindow.meta_window.disconnect(child._sizeChangedId);
            realWindow.meta_window.disconnect(child._posChangedId);
            realWindow.disconnect(child._destroyId);
        });
    }

    _onMetaWindowSizeChanged() {
        this._computeBoundingBox();
        this.emit('size-changed');
    }

    _onDestroy() {
        this._disconnectSignals();

        this.metaWindow._delegate = null;
        this.actor._delegate = null;

        if (this.inDrag) {
            this.emit('drag-end');
            this.inDrag = false;
        }

        this.disconnectAll();
    }

    _activate() {
        this._selected = true;
        this.emit('selected', global.get_current_time());
    }

    _onKeyPress(actor, event) {
        let symbol = event.get_key_symbol();
        let isEnter = (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter);
        if (isEnter) {
            this._activate();
            return true;
        }

        return false;
    }

    _onClicked(action, actor) {
        this._activate();
    }

    _onLongPress(action, actor, state) {
        // Take advantage of the Clutter policy to consider
        // a long-press canceled when the pointer movement
        // exceeds dnd-drag-threshold to manually start the drag
        if (state == Clutter.LongPressState.CANCEL) {
            let event = Clutter.get_current_event();
            this._dragTouchSequence = event.get_event_sequence();

            // A click cancels a long-press before any click handler is
            // run - make sure to not start a drag in that case
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                if (this._selected)
                    return;
                let [x, y] = action.get_coords();
                action.release();
                this._draggable.startDrag(x, y, global.get_current_time(), this._dragTouchSequence, event.get_device());
            });
        } else {
            this.emit('show-chrome');
        }
        return true;
    }

    _onDragBegin(draggable, time) {
        this._dragSlot = this._slot;
        [this.dragOrigX, this.dragOrigY] = this.actor.get_position();
        this.dragOrigScale = this.actor.scale_x;
        this.inDrag = true;
        this.emit('drag-begin');
    }

    handleDragOver(source, actor, x, y, time) {
        return this._workspace.handleDragOver(source, actor, x, y, time);
    }

    acceptDrop(source, actor, x, y, time) {
        this._workspace.acceptDrop(source, actor, x, y, time);
    }

    _onDragCancelled(draggable, time) {
        this.emit('drag-cancelled');
    }

    _onDragEnd(draggable, time, snapback) {
        this.inDrag = false;

        // We may not have a parent if DnD completed successfully, in
        // which case our clone will shortly be destroyed and replaced
        // with a new one on the target workspace.
        if (this.actor.get_parent() != null) {
            if (this._stackAbove == null)
                this.actor.lower_bottom();
            else
                this.actor.raise(this._stackAbove);
        }


        this.emit('drag-end');
    }
};
Signals.addSignalMethods(WindowClone.prototype);


/**
 * @windowClone: Corresponding window clone
 * @parentActor: The actor which will be the parent of all overlay items
 *               such as the close button and window caption
 */
var WindowOverlay = class {
    constructor(windowClone, parentActor) {
        let metaWindow = windowClone.metaWindow;

        this._windowClone = windowClone;
        this._parentActor = parentActor;
        this._hidden = false;

        this._idleHideOverlayId = 0;

        this.borderSize = 0;
        this.border = new St.Bin({ style_class: 'window-clone-border' });

        this.title = new St.Label({ style_class: 'window-caption',
                                    text: this._getCaption() });
        this.title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        windowClone.actor.label_actor = this.title;

        this._maxTitleWidth = -1;

        this._updateCaptionId = metaWindow.connect('notify::title', w => {
            this.title.text = this._getCaption();
            this.relayout(false);
        });

        this.closeButton = new St.Button({ style_class: 'window-close' });
        this.closeButton.add_actor(new St.Icon({ icon_name: 'window-close-symbolic' }));
        this.closeButton._overlap = 0;

        this.closeButton.connect('clicked', () => this._windowClone.deleteAll());

        windowClone.actor.connect('destroy', this._onDestroy.bind(this));
        windowClone.connect('show-chrome', this._onShowChrome.bind(this));
        windowClone.connect('hide-chrome', this._onHideChrome.bind(this));

        this.title.hide();
        this.closeButton.hide();

        // Don't block drop targets
        Shell.util_set_hidden_from_pick(this.title, true);
        Shell.util_set_hidden_from_pick(this.border, true);

        parentActor.add_actor(this.border);
        parentActor.add_actor(this.title);
        parentActor.add_actor(this.closeButton);
        this.title.connect('style-changed',
                           this._onStyleChanged.bind(this));
        this.closeButton.connect('style-changed',
                                 this._onStyleChanged.bind(this));
        this.border.connect('style-changed',
                            this._onStyleChanged.bind(this));

        // Force a style change if we are already on a stage - otherwise
        // the signal will be emitted normally when we are added
        if (parentActor.get_stage())
            this._onStyleChanged();
    }

    hide() {
        this._hidden = true;

        this.hideOverlay();
    }

    show() {
        this._hidden = false;

        if (this._windowClone.actor['has-pointer'])
            this._animateVisible();
    }

    chromeHeights() {
        return [Math.max(this.borderSize, this.closeButton.height - this.closeButton._overlap),
                (this.title.height - this.borderSize) / 2];
    }

    chromeWidths() {
        return [this.borderSize,
                Math.max(this.borderSize, this.closeButton.width - this.closeButton._overlap)];
    }

    setMaxChromeWidth(max) {
        if (this._maxTitleWidth == max)
            return;

        this._maxTitleWidth = max;
    }

    relayout(animate) {
        let button = this.closeButton;
        let title = this.title;
        let border = this.border;

        Tweener.removeTweens(button);
        Tweener.removeTweens(border);
        Tweener.removeTweens(title);

        let [cloneX, cloneY, cloneWidth, cloneHeight] = this._windowClone.slot;

        let layout = Meta.prefs_get_button_layout();
        let side = layout.left_buttons.indexOf(Meta.ButtonFunction.CLOSE) > -1 ? St.Side.LEFT : St.Side.RIGHT;

        let buttonX;
        let buttonY = cloneY - (button.height - button._overlap);
        if (side == St.Side.LEFT)
            buttonX = cloneX - (button.width - button._overlap);
        else
            buttonX = cloneX + (cloneWidth - button._overlap);

        if (animate)
            this._animateOverlayActor(button, Math.floor(buttonX), Math.floor(buttonY), button.width);
        else
            button.set_position(Math.floor(buttonX), Math.floor(buttonY));

        // Clutter.Actor.get_preferred_width() will return the fixed width if
        // one is set, so we need to reset the width by calling set_width(-1),
        // to forward the call down to StLabel.
        // We also need to save and restore the current width, otherwise the
        // animation starts from the wrong point.
        let prevTitleWidth = title.width;
        title.set_width(-1);

        let [titleMinWidth, titleNatWidth] = title.get_preferred_width(-1);
        let titleWidth = Math.max(titleMinWidth,
                                  Math.min(titleNatWidth, this._maxTitleWidth));
        title.width = prevTitleWidth;

        let titleX = cloneX + (cloneWidth - titleWidth) / 2;
        let titleY = cloneY + cloneHeight - (title.height - this.borderSize) / 2;

        if (animate) {
            this._animateOverlayActor(title, Math.floor(titleX), Math.floor(titleY), titleWidth);
        } else {
            title.width = titleWidth;
            title.set_position(Math.floor(titleX), Math.floor(titleY));
        }

        let borderX = cloneX - this.borderSize;
        let borderY = cloneY - this.borderSize;
        let borderWidth = cloneWidth + 2 * this.borderSize;
        let borderHeight = cloneHeight + 2 * this.borderSize;

        if (animate) {
            this._animateOverlayActor(this.border, borderX, borderY,
                                      borderWidth, borderHeight);
        } else {
            this.border.set_position(borderX, borderY);
            this.border.set_size(borderWidth, borderHeight);
        }
    }

    _getCaption() {
        let metaWindow = this._windowClone.metaWindow;
        if (metaWindow.title)
            return metaWindow.title;

        let tracker = Shell.WindowTracker.get_default();
        let app = tracker.get_window_app(metaWindow);
        return app.get_name();
    }

    _animateOverlayActor(actor, x, y, width, height) {
        let params = { x: x,
                       y: y,
                       width: width,
                       time: Overview.ANIMATION_TIME,
                       transition: 'easeOutQuad' };

        if (height !== undefined)
            params.height = height;

        Tweener.addTween(actor, params);
    }

    _windowCanClose() {
        return this._windowClone.metaWindow.can_close() &&
               !this._windowClone.hasAttachedDialogs();
    }

    _onDestroy() {
        if (this._idleHideOverlayId > 0) {
            Mainloop.source_remove(this._idleHideOverlayId);
            this._idleHideOverlayId = 0;
        }
        this._windowClone.metaWindow.disconnect(this._updateCaptionId);
        this.title.destroy();
        this.closeButton.destroy();
        this.border.destroy();
    }

    _animateVisible() {
        this._parentActor.raise_top();

        let toAnimate = [this.border, this.title];
        if (this._windowCanClose())
            toAnimate.push(this.closeButton);

        toAnimate.forEach(a => {
            a.show();
            a.opacity = 0;
            Tweener.addTween(a,
                             { opacity: 255,
                               time: WINDOW_OVERLAY_FADE_TIME,
                               transition: 'easeOutQuad' });
        });
    }

    _animateInvisible() {
        [this.closeButton, this.border, this.title].forEach(a => {
            a.opacity = 255;
            Tweener.addTween(a,
                             { opacity: 0,
                               time: WINDOW_OVERLAY_FADE_TIME,
                               transition: 'easeInQuad' });
        });
    }

    _onShowChrome() {
        // We might get enter events on the clone while the overlay is
        // hidden, e.g. during animations, we ignore these events,
        // as the close button will be shown as needed when the overlays
        // are shown again
        if (this._hidden)
            return;

        this._animateVisible();
        this.emit('chrome-visible');
    }

    _onHideChrome() {
        if (this._idleHideOverlayId == 0) {
            this._idleHideOverlayId = Mainloop.timeout_add(WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT, this._idleHideOverlay.bind(this));
            GLib.Source.set_name_by_id(this._idleHideOverlayId, '[gnome-shell] this._idleHideOverlay');
        }
    }

    _idleHideOverlay() {
        this._idleHideOverlayId = 0;

        if (!this._windowClone.actor['has-pointer'] &&
            !this.closeButton['has-pointer'])
            this._animateInvisible();

        return GLib.SOURCE_REMOVE;
    }

    hideOverlay() {
        if (this._idleHideOverlayId > 0) {
            Mainloop.source_remove(this._idleHideOverlayId);
            this._idleHideOverlayId = 0;
        }
        this.closeButton.hide();
        this.border.hide();
        this.title.hide();
    }

    _onStyleChanged() {
        let closeNode = this.closeButton.get_theme_node();
        this.closeButton._overlap = closeNode.get_length('-shell-close-overlap');

        let borderNode = this.border.get_theme_node();
        this.borderSize = borderNode.get_border_width(St.Side.TOP);

        this._parentActor.queue_relayout();
    }
};
Signals.addSignalMethods(WindowOverlay.prototype);

var WindowPositionFlags = {
    NONE: 0,
    INITIAL: 1 << 0,
    ANIMATE: 1 << 1
};

// Window Thumbnail Layout Algorithm
// =================================
//
// General overview
// ----------------
//
// The window thumbnail layout algorithm calculates some optimal layout
// by computing layouts with some number of rows, calculating how good
// each layout is, and stopping iterating when it finds one that is worse
// than the previous layout. A layout consists of which windows are in
// which rows, row sizes and other general state tracking that would make
// calculating window positions from this information fairly easy.
//
// We don't compute some global order of windows right now for optimal
// travel when animating into the overview; windows are assumed to be
// in some stable order.
//
// After a layout is computed that's considered the best layout, we
// compute the layout scale to fit it in the area, and then compute
// slots (sizes and positions) for each thumbnail.
//
// Layout generation
// -----------------
//
// Layout generation is naive and simple: we simply add windows to a row
// until we've added too many windows to a row, and then make a new row,
// until we have our required N rows. The potential issue with this strategy
// is that we may have too many windows at the bottom in some pathological
// cases, which tends to make the thumbnails have the shape of a pile of
// sand with a peak, with one window at the top.
//
// Scaling factors
// ---------------
//
// Thumbnail position is mostly straightforward -- the main issue is
// computing an optimal scale for each window that fits the constraints,
// and doesn't make the thumbnail too small to see. There are two factors
// involved in thumbnail scale to make sure that these two goals are met:
// the window scale (calculated by _computeWindowScale) and the layout
// scale (calculated by computeSizeAndScale).
//
// The calculation logic becomes slightly more complicated because row
// and column spacing are not scaled, they're constant, so we can't
// simply generate a bunch of window positions and then scale it. In
// practice, it's not too bad -- we can simply try to fit the layout
// in the input area minus whatever spacing we have, and then add
// it back afterwards.
//
// The window scale is constant for the window's size regardless of the
// input area or the layout scale or rows or anything else, and right
// now just enlarges the window if it's too small. The fact that this
// factor is stable makes it easy to calculate, so there's no sense
// in not applying it in most calculations.
//
// The layout scale depends on the input area, the rows, etc, but is the
// same for the entire layout, rather than being per-window. After
// generating the rows of windows, we basically do some basic math to
// fit the full, unscaled layout to the input area, as described above.
//
// With these two factors combined, the final scale of each thumbnail is
// simply windowScale * layoutScale... almost.
//
// There's one additional constraint: the thumbnail scale must never be
// larger than WINDOW_CLONE_MAXIMUM_SCALE, which means that the inequality:
//
//   windowScale * layoutScale <= WINDOW_CLONE_MAXIMUM_SCALE
//
// must always be true. This is for each individual window -- while we
// could adjust layoutScale to make the largest thumbnail smaller than
// WINDOW_CLONE_MAXIMUM_SCALE, it would shrink windows which are already
// under the inequality. To solve this, we simply cheat: we simply keep
// each window's "cell" area to be the same, but we shrink the thumbnail
// and center it horizontally, and align it to the bottom vertically.

var LayoutStrategy = class {
    constructor(monitor, rowSpacing, columnSpacing) {
        if (new.target === LayoutStrategy)
            throw new TypeError('Cannot instantiate abstract type ' + new.target.name);

        this._monitor = monitor;
        this._rowSpacing = rowSpacing;
        this._columnSpacing = columnSpacing;
    }

    _newRow() {
        // Row properties:
        //
        // * x, y are the position of row, relative to area
        //
        // * width, height are the scaled versions of fullWidth, fullHeight
        //
        // * width also has the spacing in between windows. It's not in
        //   fullWidth, as the spacing is constant, whereas fullWidth is
        //   meant to be scaled
        //
        // * neither height/fullHeight have any sort of spacing or padding
        return { x: 0, y: 0,
                 width: 0, height: 0,
                 fullWidth: 0, fullHeight: 0,
                 windows: [] };
    }

    // Computes and returns an individual scaling factor for @window,
    // to be applied in addition to the overal layout scale.
    _computeWindowScale(window) {
        // Since we align windows next to each other, the height of the
        // thumbnails is much more important to preserve than the width of
        // them, so two windows with equal height, but maybe differering
        // widths line up.
        let ratio = window.height / this._monitor.height;

        // The purpose of this manipulation here is to prevent windows
        // from getting too small. For something like a calculator window,
        // we need to bump up the size just a bit to make sure it looks
        // good. We'll use a multiplier of 1.5 for this.

        // Map from [0, 1] to [1.5, 1]
        return _interpolate(1.5, 1, ratio);
    }

    // Compute the size of each row, by assigning to the properties
    // row.width, row.height, row.fullWidth, row.fullHeight, and
    // (optionally) for each row in @layout.rows. This method is
    // intended to be called by subclasses.
    _computeRowSizes(layout) {
        throw new Error('_computeRowSizes not implemented');
    }

    // Compute strategy-specific window slots for each window in
    // @windows, given the @layout. The strategy may also use @layout
    // as strategy-specific storage.
    //
    // This must calculate:
    //  * maxColumns - The maximum number of columns used by the layout.
    //  * gridWidth - The total width used by the grid, unscaled, unspaced.
    //  * gridHeight - The totial height used by the grid, unscaled, unspaced.
    //  * rows - A list of rows, which should be instantiated by _newRow.
    computeLayout(windows, layout) {
        throw new Error('computeLayout not implemented');
    }

    // Given @layout, compute the overall scale and space of the layout.
    // The scale is the individual, non-fancy scale of each window, and
    // the space is the percentage of the available area eventually
    // used by the layout.

    // This method does not return anything, but instead installs
    // the properties "scale" and "space" on @layout directly.
    //
    // Make sure to call this methods before calling computeWindowSlots(),
    // as it depends on the scale property installed in @layout here.
    computeScaleAndSpace(layout) {
        let area = layout.area;

        let hspacing = (layout.maxColumns - 1) * this._columnSpacing;
        let vspacing = (layout.numRows - 1) * this._rowSpacing;

        let spacedWidth = area.width - hspacing;
        let spacedHeight = area.height - vspacing;

        let horizontalScale = spacedWidth / layout.gridWidth;
        let verticalScale = spacedHeight / layout.gridHeight;

        // Thumbnails should be less than 70% of the original size
        let scale = Math.min(horizontalScale, verticalScale, WINDOW_CLONE_MAXIMUM_SCALE);

        let scaledLayoutWidth = layout.gridWidth * scale + hspacing;
        let scaledLayoutHeight = layout.gridHeight * scale + vspacing;
        let space = (scaledLayoutWidth * scaledLayoutHeight) / (area.width * area.height);

        layout.scale = scale;
        layout.space = space;
    }

    computeWindowSlots(layout, area) {
        this._computeRowSizes(layout);

        let { rows: rows, scale: scale } = layout;

        let slots = [];

        // Do this in three parts.
        let heightWithoutSpacing = 0;
        for (let i = 0; i < rows.length; i++) {
            let row = rows[i];
            heightWithoutSpacing += row.height;
        }

        let verticalSpacing = (rows.length - 1) * this._rowSpacing;
        let additionalVerticalScale = Math.min(1, (area.height - verticalSpacing) / heightWithoutSpacing);

        // keep track how much smaller the grid becomes due to scaling
        // so it can be centered again
        let compensation = 0
        let y = 0;

        for (let i = 0; i < rows.length; i++) {
            let row = rows[i];

            // If this window layout row doesn't fit in the actual
            // geometry, then apply an additional scale to it.
            let horizontalSpacing = (row.windows.length - 1) * this._columnSpacing;
            let widthWithoutSpacing = row.width - horizontalSpacing;
            let additionalHorizontalScale = Math.min(1, (area.width - horizontalSpacing) / widthWithoutSpacing);

            if (additionalHorizontalScale < additionalVerticalScale) {
                row.additionalScale = additionalHorizontalScale;
                // Only consider the scaling in addition to the vertical scaling for centering.
                compensation += (additionalVerticalScale - additionalHorizontalScale) * row.height;
            } else {
                row.additionalScale = additionalVerticalScale;
                // No compensation when scaling vertically since centering based on a too large
                // height would undo what vertical scaling is trying to achieve.
            }

            row.x = area.x + (Math.max(area.width - (widthWithoutSpacing * row.additionalScale + horizontalSpacing), 0) / 2)
            row.y = area.y + (Math.max(area.height - (heightWithoutSpacing + verticalSpacing), 0) / 2) + y;
            y += row.height * row.additionalScale + this._rowSpacing;
        }

        compensation = compensation / 2;

        for (let i = 0; i < rows.length; i++) {
            let row = rows[i];
            let x = row.x;
            for (let j = 0; j < row.windows.length; j++) {
                let window = row.windows[j];

                let s = scale * this._computeWindowScale(window) * row.additionalScale;
                let cellWidth = window.width * s;
                let cellHeight = window.height * s;

                s = Math.min(s, WINDOW_CLONE_MAXIMUM_SCALE);
                let cloneWidth = window.width * s;

                let cloneX = x + (cellWidth - cloneWidth) / 2;
                let cloneY = row.y + row.height * row.additionalScale - cellHeight + compensation;

                // Align with the pixel grid to prevent blurry windows at scale = 1
                cloneX = Math.floor(cloneX);
                cloneY = Math.floor(cloneY);

                slots.push([cloneX, cloneY, s, window]);
                x += cellWidth + this._columnSpacing;
            }
        }
        return slots;
    }
};

var UnalignedLayoutStrategy = class extends LayoutStrategy {
    _computeRowSizes(layout) {
        let { rows: rows, scale: scale } = layout;
        for (let i = 0; i < rows.length; i++) {
            let row = rows[i];
            row.width = row.fullWidth * scale + (row.windows.length - 1) * this._columnSpacing;
            row.height = row.fullHeight * scale;
        }
    }

    _keepSameRow(row, window, width, idealRowWidth) {
        if (row.fullWidth + width <= idealRowWidth)
            return true;

        let oldRatio = row.fullWidth / idealRowWidth;
        let newRatio = (row.fullWidth + width) / idealRowWidth;

        if (Math.abs(1 - newRatio) < Math.abs(1 - oldRatio))
            return true;

        return false;
    }

    _sortRow(row) {
        // Sort windows horizontally to minimize travel distance
        row.windows.sort((a, b) => a.realWindow.x - b.realWindow.x);
    }

    computeLayout(windows, layout) {
        let numRows = layout.numRows;

        let rows = [];
        let totalWidth = 0;
        for (let i = 0; i < windows.length; i++) {
            let window = windows[i];
            let s = this._computeWindowScale(window);
            totalWidth += window.width * s;
        }

        let idealRowWidth = totalWidth / numRows;
        let windowIdx = 0;
        for (let i = 0; i < numRows; i++) {
            let col = 0;
            let row = this._newRow();
            rows.push(row);

            for (; windowIdx < windows.length; windowIdx++) {
                let window = windows[windowIdx];
                let s = this._computeWindowScale(window);
                let width = window.width * s;
                let height = window.height * s;
                row.fullHeight = Math.max(row.fullHeight, height);

                // either new width is < idealWidth or new width is nearer from idealWidth then oldWidth
                if (this._keepSameRow(row, window, width, idealRowWidth) || (i == numRows - 1)) {
                    row.windows.push(window);
                    row.fullWidth += width;
                } else {
                    break;
                }
            }
        }

        let gridHeight = 0;
        let maxRow;
        for (let i = 0; i < numRows; i++) {
            let row = rows[i];
            this._sortRow(row);

            if (!maxRow || row.fullWidth > maxRow.fullWidth)
                maxRow = row;
            gridHeight += row.fullHeight;
        }

        layout.rows = rows;
        layout.maxColumns = maxRow.windows.length;
        layout.gridWidth = maxRow.fullWidth;
        layout.gridHeight = gridHeight;
    }
};

function padArea(area, padding) {
    return {
        x: area.x + padding.left,
        y: area.y + padding.top,
        width: area.width - padding.left - padding.right,
        height: area.height - padding.top - padding.bottom,
    };
}

function rectEqual(one, two) {
    if (one == two)
        return true;

    if (!one || !two)
        return false;

    return (one.x == two.x &&
            one.y == two.y &&
            one.width == two.width &&
            one.height == two.height);
}

const WorkspaceActor = GObject.registerClass(
class WorkspaceActor extends St.Widget {
    vfunc_get_focus_chain() {
        return this.get_children().filter(c => c.visible).sort((a,b) => {
            let cloneA = (a._delegate && a._delegate instanceof WindowClone) ? a._delegate: null;
            let cloneB = (b._delegate && b._delegate instanceof WindowClone) ? b._delegate: null;
            if (cloneA && cloneB)
                return cloneA.slotId - cloneB.slotId;

            return 0;
        });
    }
});

/**
 * @metaWorkspace: a #Meta.Workspace, or null
 */
var Workspace = class {
    constructor(metaWorkspace, monitorIndex) {
        // When dragging a window, we use this slot for reserve space.
        this._reservedSlot = null;
        this._reservedSlotWindow = null;
        this.metaWorkspace = metaWorkspace;

        // The full geometry is the geometry we should try and position
        // windows for. The actual geometry we allocate may be less than
        // this, like if the workspace switcher is slid out.
        this._fullGeometry = null;

        // The actual geometry is the geometry we need to arrange windows
        // in. If this is a smaller area than the full geometry, we'll
        // do some simple aspect ratio like math to fit the layout calculated
        // for the full geometry into this area.
        this._actualGeometry = null;
        this._actualGeometryLater = 0;

        this._currentLayout = null;

        this.monitorIndex = monitorIndex;
        this._monitor = Main.layoutManager.monitors[this.monitorIndex];
        this._windowOverlaysGroup = new Clutter.Actor();
        // Without this the drop area will be overlapped.
        this._windowOverlaysGroup.set_size(0, 0);

        this.actor = new WorkspaceActor({ style_class: 'window-picker' });
        if (monitorIndex != Main.layoutManager.primaryIndex)
            this.actor.add_style_class_name('external-monitor');
        this.actor.set_size(0, 0);

        this._dropRect = new Clutter.Actor({ opacity: 0 });
        this._dropRect._delegate = this;

        this.actor.add_actor(this._dropRect);
        this.actor.add_actor(this._windowOverlaysGroup);

        this.actor.connect('destroy', this._onDestroy.bind(this));

        let windows = global.get_window_actors().filter(this._isMyWindow, this);

        // Create clones for windows that should be
        // visible in the Overview
        this._windows = [];
        this._windowOverlays = [];
        for (let i = 0; i < windows.length; i++) {
            if (this._isOverviewWindow(windows[i]))
                this._addWindowClone(windows[i], true);
        }

        // Track window changes
        if (this.metaWorkspace) {
            this._windowAddedId = this.metaWorkspace.connect('window-added',
                                                             this._windowAdded.bind(this));
            this._windowRemovedId = this.metaWorkspace.connect('window-removed',
                                                               this._windowRemoved.bind(this));
        }
        this._windowEnteredMonitorId = global.display.connect('window-entered-monitor',
                                                              this._windowEnteredMonitor.bind(this));
        this._windowLeftMonitorId = global.display.connect('window-left-monitor',
                                                           this._windowLeftMonitor.bind(this));
        this._repositionWindowsId = 0;

        this.leavingOverview = false;

        this._positionWindowsFlags = 0;
        this._positionWindowsId = 0;

        this.actor.connect('notify::mapped', () => {
            if (this.actor.mapped)
                this._syncActualGeometry();
        });
    }

    setFullGeometry(geom) {
        if (rectEqual(this._fullGeometry, geom))
            return;

        this._fullGeometry = geom;

        if (this.actor.mapped)
            this._recalculateWindowPositions(WindowPositionFlags.NONE);
    }

    setActualGeometry(geom) {
        if (rectEqual(this._actualGeometry, geom))
            return;

        this._actualGeometry = geom;
        this._actualGeometryDirty = true;

        if (this.actor.mapped)
            this._syncActualGeometry();
    }

    _syncActualGeometry() {
        if (this._actualGeometryLater || !this._actualGeometryDirty)
            return;
        if (!this._actualGeometry)
            return;

        this._actualGeometryLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            this._actualGeometryLater = 0;
            if (!this.actor.mapped)
                return false;

            let geom = this._actualGeometry;

            this._dropRect.set_position(geom.x, geom.y);
            this._dropRect.set_size(geom.width, geom.height);
            this._updateWindowPositions(Main.overview.animationInProgress ? WindowPositionFlags.ANIMATE : WindowPositionFlags.NONE);

            return false;
        });
    }

    _lookupIndex(metaWindow) {
        return this._windows.findIndex(w => w.metaWindow == metaWindow);
    }

    containsMetaWindow(metaWindow) {
        return this._lookupIndex(metaWindow) >= 0;
    }

    isEmpty() {
        return this._windows.length == 0;
    }

    setReservedSlot(metaWindow) {
        if (this._reservedSlotWindow == metaWindow)
            return;

        if (!metaWindow || this.containsMetaWindow(metaWindow)) {
            this._reservedSlotWindow = null;
            this._reservedSlot = null;
        } else {
            this._reservedSlotWindow = metaWindow;
            this._reservedSlot = this._windows[this._lookupIndex(metaWindow)];
        }

        this._recalculateWindowPositions(WindowPositionFlags.ANIMATE);
    }

    _recalculateWindowPositions(flags) {
        this._positionWindowsFlags |= flags;

        if (this._positionWindowsId > 0)
            return;

        this._positionWindowsId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            this._realRecalculateWindowPositions(this._positionWindowsFlags);
            this._positionWindowsFlags = 0;
            this._positionWindowsId = 0;
            return false;
        });
    }

    _realRecalculateWindowPositions(flags) {
        if (this._repositionWindowsId > 0) {
            Mainloop.source_remove(this._repositionWindowsId);
            this._repositionWindowsId = 0;
        }

        let clones = this._windows.slice();
        if (clones.length == 0)
            return;

        clones.sort((a, b) => {
            return a.metaWindow.get_stable_sequence() - b.metaWindow.get_stable_sequence();
        });

        if (this._reservedSlot)
            clones.push(this._reservedSlot);

        this._currentLayout = this._computeLayout(clones);
        this._updateWindowPositions(flags);
    }

    _updateWindowPositions(flags) {
        if (this._currentLayout == null) {
            this._recalculateWindowPositions(flags);
            return;
        }

        // We will reposition windows anyway when enter again overview or when ending the windows
        // animations whith fade animation.
        // In this way we avoid unwanted animations of windows repositioning while
        // animating overview.
        if (this.leavingOverview || this._animatingWindowsFade)
            return;

        let initialPositioning = flags & WindowPositionFlags.INITIAL;
        let animate = flags & WindowPositionFlags.ANIMATE;

        let layout = this._currentLayout;
        let strategy = layout.strategy;

        let [, , padding] = this._getSpacingAndPadding();
        let area = padArea(this._actualGeometry, padding);
        let slots = strategy.computeWindowSlots(layout, area);

        let workspaceManager = global.workspace_manager;
        let currentWorkspace = workspaceManager.get_active_workspace();
        let isOnCurrentWorkspace = this.metaWorkspace == null || this.metaWorkspace == currentWorkspace;

        for (let i = 0; i < slots.length; i++) {
            let slot = slots[i];
            let [x, y, scale, clone] = slot;

            clone.slotId = i;

            // Positioning a window currently being dragged must be avoided;
            // we'll just leave a blank spot in the layout for it.
            if (clone.inDrag)
                continue;

            let cloneWidth = clone.actor.width * scale;
            let cloneHeight = clone.actor.height * scale;
            clone.slot = [x, y, cloneWidth, cloneHeight];

            let cloneCenter = x + cloneWidth / 2;
            let maxChromeWidth = 2 * Math.min(
                cloneCenter - area.x,
                area.x + area.width - cloneCenter);
            clone.overlay.setMaxChromeWidth(Math.round(maxChromeWidth));

            if (clone.overlay && (initialPositioning || !clone.positioned))
                clone.overlay.hide();

            if (!clone.positioned) {
                // This window appeared after the overview was already up
                // Grow the clone from the center of the slot
                clone.actor.x = x + cloneWidth / 2;
                clone.actor.y = y + cloneHeight / 2;
                clone.actor.scale_x = 0;
                clone.actor.scale_y = 0;
                clone.positioned = true;
            }

            if (animate && isOnCurrentWorkspace) {
                if (!clone.metaWindow.showing_on_its_workspace()) {
                    /* Hidden windows should fade in and grow
                     * therefore we need to resize them now so they
                     * can be scaled up later */
                    if (initialPositioning) {
                        clone.actor.opacity = 0;
                        clone.actor.scale_x = 0;
                        clone.actor.scale_y = 0;
                        clone.actor.x = x;
                        clone.actor.y = y;
                    }

                    Tweener.addTween(clone.actor,
                                     { opacity: 255,
                                       time: Overview.ANIMATION_TIME,
                                       transition: 'easeInQuad'
                                     });
                }

                this._animateClone(clone, clone.overlay, x, y, scale);
            } else {
                // cancel any active tweens (otherwise they might override our changes)
                Tweener.removeTweens(clone.actor);
                clone.actor.set_position(x, y);
                clone.actor.set_scale(scale, scale);
                clone.actor.set_opacity(255);
                clone.overlay.relayout(false);
                this._showWindowOverlay(clone, clone.overlay);
            }
        }
    }

    syncStacking(stackIndices) {
        let clones = this._windows.slice();
        clones.sort((a, b) => {
            let indexA = stackIndices[a.metaWindow.get_stable_sequence()];
            let indexB = stackIndices[b.metaWindow.get_stable_sequence()];
            return indexA - indexB;
        });

        for (let i = 0; i < clones.length; i++) {
            let clone = clones[i];
            let metaWindow = clone.metaWindow;
            if (i == 0) {
                clone.setStackAbove(this._dropRect);
            } else {
                let previousClone = clones[i - 1];
                clone.setStackAbove(previousClone.actor);
            }
        }
    }

    _animateClone(clone, overlay, x, y, scale) {
        Tweener.addTween(clone.actor,
                         { x: x,
                           y: y,
                           scale_x: scale,
                           scale_y: scale,
                           time: Overview.ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this._showWindowOverlay(clone, overlay);
                           }
                         });

        clone.overlay.relayout(true);
    }

    _showWindowOverlay(clone, overlay) {
        if (clone.inDrag)
            return;

        if (overlay && overlay._hidden)
                overlay.show();
    }

    _delayedWindowRepositioning() {
        let [x, y, mask] = global.get_pointer();

        let pointerHasMoved = (this._cursorX != x && this._cursorY != y);
        let inWorkspace = (this._fullGeometry.x < x && x < this._fullGeometry.x + this._fullGeometry.width &&
                           this._fullGeometry.y < y && y < this._fullGeometry.y + this._fullGeometry.height);

        if (pointerHasMoved && inWorkspace) {
            // store current cursor position
            this._cursorX = x;
            this._cursorY = y;
            return GLib.SOURCE_CONTINUE;
        }

        let actorUnderPointer = global.stage.get_actor_at_pos(Clutter.PickMode.REACTIVE, x, y);
        for (let i = 0; i < this._windows.length; i++) {
            if (this._windows[i].actor == actorUnderPointer)
                return GLib.SOURCE_CONTINUE;
        }

        this._recalculateWindowPositions(WindowPositionFlags.ANIMATE);
        this._repositionWindowsId = 0;
        return GLib.SOURCE_REMOVE;
    }

    _doRemoveWindow(metaWin) {
        let win = metaWin.get_compositor_private();

        let clone = this._removeWindowClone(metaWin);

        if (clone) {
            // If metaWin.get_compositor_private() returned non-NULL, that
            // means the window still exists (and is just being moved to
            // another workspace or something), so set its overviewHint
            // accordingly. (If it returned NULL, then the window is being
            // destroyed; we'd like to animate this, but it's too late at
            // this point.)
            if (win) {
                let [stageX, stageY] = clone.actor.get_transformed_position();
                let [stageWidth, stageHeight] = clone.actor.get_transformed_size();
                win._overviewHint = {
                    x: stageX,
                    y: stageY,
                    scale: stageWidth / clone.actor.width
                };
            }
            clone.destroy();
        }

        // We need to reposition the windows; to avoid shuffling windows
        // around while the user is interacting with the workspace, we delay
        // the positioning until the pointer remains still for at least 750 ms
        // or is moved outside the workspace

        // remove old handler
        if (this._repositionWindowsId > 0) {
            Mainloop.source_remove(this._repositionWindowsId);
            this._repositionWindowsId = 0;
        }

        // setup new handler
        let [x, y, mask] = global.get_pointer();
        this._cursorX = x;
        this._cursorY = y;

        this._currentLayout = null;
        this._repositionWindowsId = Mainloop.timeout_add(WINDOW_REPOSITIONING_DELAY,
            this._delayedWindowRepositioning.bind(this));
        GLib.Source.set_name_by_id(this._repositionWindowsId, '[gnome-shell] this._delayedWindowRepositioning');
    }

    _doAddWindow(metaWin) {
        if (this.leavingOverview)
            return;

        let win = metaWin.get_compositor_private();

        if (!win) {
            // Newly-created windows are added to a workspace before
            // the compositor finds out about them...
            let id = Mainloop.idle_add(() => {
                if (this.actor &&
                    metaWin.get_compositor_private() &&
                    metaWin.get_workspace() == this.metaWorkspace)
                    this._doAddWindow(metaWin);
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(id, '[gnome-shell] this._doAddWindow');
            return;
        }

        // We might have the window in our list already if it was on all workspaces and
        // now was moved to this workspace
        if (this._lookupIndex(metaWin) != -1)
            return;

        if (!this._isMyWindow(win))
            return;

        if (!this._isOverviewWindow(win)) {
            if (metaWin.get_transient_for() == null)
                return;

            // Let the top-most ancestor handle all transients
            let parent = metaWin.find_root_ancestor();
            let clone = this._windows.find(c => c.metaWindow == parent);

            // If no clone was found, the parent hasn't been created yet
            // and will take care of the dialog when added
            if (clone)
                clone.addDialog(metaWin);

            return;
        }

        let [clone, overlay] = this._addWindowClone(win, false);

        if (win._overviewHint) {
            let x = win._overviewHint.x - this.actor.x;
            let y = win._overviewHint.y - this.actor.y;
            let scale = win._overviewHint.scale;
            delete win._overviewHint;

            clone.slot = [x, y, clone.actor.width * scale, clone.actor.height * scale];
            clone.positioned = true;

            clone.actor.set_position(x, y);
            clone.actor.set_scale(scale, scale);
            clone.overlay.relayout(false);
        }

        this._currentLayout = null;
        this._recalculateWindowPositions(WindowPositionFlags.ANIMATE);
    }

    _windowAdded(metaWorkspace, metaWin) {
        this._doAddWindow(metaWin);
    }

    _windowRemoved(metaWorkspace, metaWin) {
        this._doRemoveWindow(metaWin);
    }

    _windowEnteredMonitor(metaDisplay, monitorIndex, metaWin) {
        if (monitorIndex == this.monitorIndex) {
            this._doAddWindow(metaWin);
        }
    }

    _windowLeftMonitor(metaDisplay, monitorIndex, metaWin) {
        if (monitorIndex == this.monitorIndex) {
            this._doRemoveWindow(metaWin);
        }
    }

    // check for maximized windows on the workspace
    hasMaximizedWindows() {
        for (let i = 0; i < this._windows.length; i++) {
            let metaWindow = this._windows[i].metaWindow;
            if (metaWindow.showing_on_its_workspace() &&
                metaWindow.maximized_horizontally &&
                metaWindow.maximized_vertically)
                return true;
        }
        return false;
    }

    fadeToOverview() {
        // We don't want to reposition windows while animating in this way.
        this._animatingWindowsFade = true;
        this._overviewShownId = Main.overview.connect('shown', this._doneShowingOverview.bind(this));
        if (this._windows.length == 0)
            return;

        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        if (this.metaWorkspace != null && this.metaWorkspace != activeWorkspace)
            return;

        // Special case maximized windows, since it doesn't make sense
        // to animate windows below in the stack
        let topMaximizedWindow;
        // It is ok to treat the case where there is no maximized
        // window as if the bottom-most window was maximized given that
        // it won't affect the result of the animation
        for (topMaximizedWindow = this._windows.length - 1; topMaximizedWindow > 0; topMaximizedWindow--) {
            let metaWindow = this._windows[topMaximizedWindow].metaWindow;
            if (metaWindow.maximized_horizontally && metaWindow.maximized_vertically)
                break;
        }

        let nTimeSlots = Math.min(WINDOW_ANIMATION_MAX_NUMBER_BLENDING + 1, this._windows.length - topMaximizedWindow);
        let windowBaseTime = Overview.ANIMATION_TIME / nTimeSlots;

        let topIndex = this._windows.length - 1;
        for (let i = 0; i < this._windows.length; i++) {
            if (i < topMaximizedWindow) {
                // below top-most maximized window, don't animate
                let overlay = this._windowOverlays[i];
                if (overlay)
                    overlay.hide();
                this._windows[i].actor.opacity = 0;
            } else {
                let fromTop = topIndex - i;
                let time;
                if (fromTop < nTimeSlots) // animate top-most windows gradually
                    time = windowBaseTime * (nTimeSlots - fromTop);
                else
                    time = windowBaseTime;

                this._windows[i].actor.opacity = 255;
                this._fadeWindow(i, time, 0);
            }
        }
    }

    fadeFromOverview() {
        this.leavingOverview = true;
        this._overviewHiddenId = Main.overview.connect('hidden', this._doneLeavingOverview.bind(this));
        if (this._windows.length == 0)
            return;

        for (let i = 0; i < this._windows.length; i++) {
            let clone = this._windows[i];
            Tweener.removeTweens(clone.actor);
        }

        if (this._repositionWindowsId > 0) {
            Mainloop.source_remove(this._repositionWindowsId);
            this._repositionWindowsId = 0;
        }

        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        if (this.metaWorkspace != null && this.metaWorkspace != activeWorkspace)
            return;

        // Special case maximized windows, since it doesn't make sense
        // to animate windows below in the stack
        let topMaximizedWindow;
        // It is ok to treat the case where there is no maximized
        // window as if the bottom-most window was maximized given that
        // it won't affect the result of the animation
        for (topMaximizedWindow = this._windows.length - 1; topMaximizedWindow > 0; topMaximizedWindow--) {
            let metaWindow = this._windows[topMaximizedWindow].metaWindow;
            if (metaWindow.maximized_horizontally && metaWindow.maximized_vertically)
                break;
        }

        let nTimeSlots = Math.min(WINDOW_ANIMATION_MAX_NUMBER_BLENDING + 1, this._windows.length - topMaximizedWindow);
        let windowBaseTime = Overview.ANIMATION_TIME / nTimeSlots;

        let topIndex = this._windows.length - 1;
        for (let i = 0; i < this._windows.length; i++) {
            if (i < topMaximizedWindow) {
                // below top-most maximized window, don't animate
                let overlay = this._windowOverlays[i];
                if (overlay)
                    overlay.hide();
                this._windows[i].actor.opacity = 0;
            } else {
                let fromTop = topIndex - i;
                let time;
                if (fromTop < nTimeSlots) // animate top-most windows gradually
                    time = windowBaseTime * (fromTop + 1);
                else
                    time = windowBaseTime * nTimeSlots;

                this._windows[i].actor.opacity = 0;
                this._fadeWindow(i, time, 255);
            }
        }
    }

    _fadeWindow(index, time, opacity) {
        let clone = this._windows[index];
        let overlay = this._windowOverlays[index];

        if (overlay)
            overlay.hide();

        if (clone.metaWindow.showing_on_its_workspace()) {
            let [origX, origY] = clone.getOriginalPosition();
            clone.actor.scale_x = 1;
            clone.actor.scale_y = 1;
            clone.actor.x = origX;
            clone.actor.y = origY;
            Tweener.addTween(clone.actor,
                             { time: time,
                               opacity: opacity,
                               transition: 'easeOutQuad'
                             });
        } else {
            // The window is hidden
            clone.actor.opacity = 0;
        }
    }

    zoomToOverview() {
        // Position and scale the windows.
        this._recalculateWindowPositions(WindowPositionFlags.ANIMATE | WindowPositionFlags.INITIAL);
    }

    zoomFromOverview() {
        let workspaceManager = global.workspace_manager;
        let currentWorkspace = workspaceManager.get_active_workspace();

        this.leavingOverview = true;

        for (let i = 0; i < this._windows.length; i++) {
            let clone = this._windows[i];
            Tweener.removeTweens(clone.actor);
        }

        if (this._repositionWindowsId > 0) {
            Mainloop.source_remove(this._repositionWindowsId);
            this._repositionWindowsId = 0;
        }
        this._overviewHiddenId = Main.overview.connect('hidden', this._doneLeavingOverview.bind(this));

        if (this.metaWorkspace != null && this.metaWorkspace != currentWorkspace)
            return;

        // Position and scale the windows.
        for (let i = 0; i < this._windows.length; i++)
           this._zoomWindowFromOverview(i);
    }

    _zoomWindowFromOverview(index) {
        let clone = this._windows[index];
        let overlay = this._windowOverlays[index];

        if (overlay)
            overlay.hide();

        if (clone.metaWindow.showing_on_its_workspace()) {
            let [origX, origY] = clone.getOriginalPosition();
            Tweener.addTween(clone.actor,
                             { x: origX,
                               y: origY,
                               scale_x: 1.0,
                               scale_y: 1.0,
                               time: Overview.ANIMATION_TIME,
                               opacity: 255,
                               transition: 'easeOutQuad'
                             });
        } else {
            // The window is hidden, make it shrink and fade it out
            Tweener.addTween(clone.actor,
                             { scale_x: 0,
                               scale_y: 0,
                               opacity: 0,
                               time: Overview.ANIMATION_TIME,
                               transition: 'easeOutQuad'
                             });
        }
    }

    destroy() {
        this.actor.destroy();
    }

    _onDestroy(actor) {
        if (this._overviewHiddenId) {
            Main.overview.disconnect(this._overviewHiddenId);
            this._overviewHiddenId = 0;
        }
        Tweener.removeTweens(actor);

        if (this.metaWorkspace) {
            this.metaWorkspace.disconnect(this._windowAddedId);
            this.metaWorkspace.disconnect(this._windowRemovedId);
        }
        global.display.disconnect(this._windowEnteredMonitorId);
        global.display.disconnect(this._windowLeftMonitorId);

        if (this._repositionWindowsId > 0) {
            Mainloop.source_remove(this._repositionWindowsId);
            this._repositionWindowsId = 0;
        }

        if (this._positionWindowsId > 0) {
            Meta.later_remove(this._positionWindowsId);
            this._positionWindowsId = 0;
        }

        if (this._actualGeometryLater > 0) {
            Meta.later_remove(this._actualGeometryLater);
            this._actualGeometryLater = 0;
        }

        this._windows = [];
    }

    // Sets this.leavingOverview flag to false.
    _doneLeavingOverview() {
        this.leavingOverview = false;
    }

    _doneShowingOverview() {
        this._animatingWindowsFade = false;
        this._recalculateWindowPositions(WindowPositionFlags.INITIAL);
    }

    // Tests if @actor belongs to this workspaces and monitor
    _isMyWindow(actor) {
        let win = actor.meta_window;
        return (this.metaWorkspace == null || win.located_on_workspace(this.metaWorkspace)) &&
            (win.get_monitor() == this.monitorIndex);
    }

    // Tests if @win should be shown in the Overview
    _isOverviewWindow(win) {
        return !win.get_meta_window().skip_taskbar;
    }

    // Create a clone of a (non-desktop) window and add it to the window list
    _addWindowClone(win, positioned) {
        let clone = new WindowClone(win, this);
        let overlay = new WindowOverlay(clone, this._windowOverlaysGroup);
        clone.overlay = overlay;
        clone.positioned = positioned;

        clone.connect('selected',
                      this._onCloneSelected.bind(this));
        clone.connect('drag-begin', () => {
            Main.overview.beginWindowDrag(clone.metaWindow);
            overlay.hide();
        });
        clone.connect('drag-cancelled', () => {
            Main.overview.cancelledWindowDrag(clone.metaWindow);
        });
        clone.connect('drag-end', () => {
            Main.overview.endWindowDrag(clone.metaWindow);
            overlay.show();
        });
        clone.connect('size-changed', () => {
            this._recalculateWindowPositions(WindowPositionFlags.NONE);
        });
        clone.actor.connect('destroy', () => {
            this._removeWindowClone(clone.metaWindow);
        });

        this.actor.add_actor(clone.actor);

        overlay.connect('chrome-visible', () => {
            let focus = global.stage.key_focus;
            if (focus == null || this.actor.contains(focus))
                clone.actor.grab_key_focus();

            this._windowOverlays.forEach(o => {
                if (o != overlay)
                    o.hideOverlay();
            });
        });

        if (this._windows.length == 0)
            clone.setStackAbove(null);
        else
            clone.setStackAbove(this._windows[this._windows.length - 1].actor);

        this._windows.push(clone);
        this._windowOverlays.push(overlay);

        return [clone, overlay];
    }

    _removeWindowClone(metaWin) {
        // find the position of the window in our list
        let index = this._lookupIndex(metaWin);

        if (index == -1)
            return null;

        this._windowOverlays.splice(index, 1);
        return this._windows.splice(index, 1).pop();
    }

    _isBetterLayout(oldLayout, newLayout) {
        if (oldLayout.scale === undefined)
            return true;

        let spacePower = (newLayout.space - oldLayout.space) * LAYOUT_SPACE_WEIGHT;
        let scalePower = (newLayout.scale - oldLayout.scale) * LAYOUT_SCALE_WEIGHT;

        if (newLayout.scale > oldLayout.scale && newLayout.space > oldLayout.space) {
            // Win win -- better scale and better space
            return true;
        } else if (newLayout.scale > oldLayout.scale && newLayout.space <= oldLayout.space) {
            // Keep new layout only if scale gain outweights aspect space loss
            return scalePower > spacePower;
        } else if (newLayout.scale <= oldLayout.scale && newLayout.space > oldLayout.space) {
            // Keep new layout only if aspect space gain outweights scale loss
            return spacePower > scalePower;
        } else {
            // Lose -- worse scale and space
            return false;
        }
    }

    _getBestLayout(windows, area, rowSpacing, columnSpacing) {
        // We look for the largest scale that allows us to fit the
        // largest row/tallest column on the workspace.

        let lastLayout = {};

        let strategy = new UnalignedLayoutStrategy(this._monitor, rowSpacing, columnSpacing);

        for (let numRows = 1; ; numRows++) {
            let numColumns = Math.ceil(windows.length / numRows);

            // If adding a new row does not change column count just stop
            // (for instance: 9 windows, with 3 rows -> 3 columns, 4 rows ->
            // 3 columns as well => just use 3 rows then)
            if (numColumns == lastLayout.numColumns)
                break;

            let layout = { area: area, strategy: strategy, numRows: numRows, numColumns: numColumns };
            strategy.computeLayout(windows, layout);
            strategy.computeScaleAndSpace(layout);

            if (!this._isBetterLayout(lastLayout, layout))
                break;

            lastLayout = layout;
        }

        return lastLayout;
    }

    _getSpacingAndPadding() {
        let node = this.actor.get_theme_node();

        // Window grid spacing
        let columnSpacing = node.get_length('-horizontal-spacing');
        let rowSpacing = node.get_length('-vertical-spacing');
        let padding = {
            left: node.get_padding(St.Side.LEFT),
            top: node.get_padding(St.Side.TOP),
            bottom: node.get_padding(St.Side.BOTTOM),
            right: node.get_padding(St.Side.RIGHT),
        };

        // All of the overlays have the same chrome sizes,
        // so just pick the first one.
        let overlay = this._windowOverlays[0];
        let [topBorder, bottomBorder] = overlay.chromeHeights();
        let [leftBorder, rightBorder] = overlay.chromeWidths();

        rowSpacing += (topBorder + bottomBorder) / 2;
        columnSpacing += (rightBorder + leftBorder) / 2;
        padding.top += topBorder;
        padding.bottom += bottomBorder;
        padding.left += leftBorder;
        padding.right += rightBorder;

        return [rowSpacing, columnSpacing, padding];
    }

    _computeLayout(windows) {
        let [rowSpacing, columnSpacing, padding] = this._getSpacingAndPadding();
        let area = padArea(this._fullGeometry, padding);
        return this._getBestLayout(windows, area, rowSpacing, columnSpacing);
    }

    _onCloneSelected(clone, time) {
        let wsIndex = undefined;
        if (this.metaWorkspace)
            wsIndex = this.metaWorkspace.index();
        Main.activateWindow(clone.metaWindow, time, wsIndex);
    }

    // Draggable target interface
    handleDragOver(source, actor, x, y, time) {
        if (source.realWindow && !this._isMyWindow(source.realWindow))
            return DND.DragMotionResult.MOVE_DROP;
        if (source.shellWorkspaceLaunch)
            return DND.DragMotionResult.COPY_DROP;

        return DND.DragMotionResult.CONTINUE;
    }

    acceptDrop(source, actor, x, y, time) {
        if (source.realWindow) {
            let win = source.realWindow;
            if (this._isMyWindow(win))
                return false;

            // Set a hint on the Mutter.Window so its initial position
            // in the new workspace will be correct
            win._overviewHint = {
                x: actor.x,
                y: actor.y,
                scale: actor.scale_x
            };

            let metaWindow = win.get_meta_window();

            // We need to move the window before changing the workspace, because
            // the move itself could cause a workspace change if the window enters
            // the primary monitor
            if (metaWindow.get_monitor() != this.monitorIndex)
                metaWindow.move_to_monitor(this.monitorIndex);

            let workspaceManager = global.workspace_manager;
            let index = this.metaWorkspace ? this.metaWorkspace.index() : workspaceManager.get_active_workspace_index();
            metaWindow.change_workspace_by_index(index, false);
            return true;
        } else if (source.shellWorkspaceLaunch) {
            source.shellWorkspaceLaunch({ workspace: this.metaWorkspace ? this.metaWorkspace.index() : -1,
                                          timestamp: time });
            return true;
        }

        return false;
    }
};
Signals.addSignalMethods(Workspace.prototype);
(uuay)remoteAccess.jsL	// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Meta = imports.gi.Meta;

const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

var RemoteAccessApplet = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        let backend = Meta.get_backend();
        let controller = backend.get_remote_access_controller();

        if (!controller)
            return;

        // We can't possibly know about all types of screen sharing on X11, so
        // showing these controls on X11 might give a false sense of security.
        // Thus, only enable these controls when using Wayland, where we are
        // in control of sharing.
        if (!Meta.is_wayland_compositor())
            return;

        this._handles = new Set();
        this._indicator = null;
        this._menuSection = null;

        controller.connect('new-handle', (controller, handle) => {
            this._onNewHandle(handle);
        });
    }

    _ensureControls() {
        if (this._indicator)
            return;

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'screen-shared-symbolic';
        this._indicator.add_style_class_name('remote-access-indicator');
        this._item =
            new PopupMenu.PopupSubMenuMenuItem(_("Screen is Being Shared"),
                                               true);
        this._item.menu.addAction(_("Turn off"),
                                  () => {
                                      for (let handle of this._handles)
                                            handle.stop();
                                  });
        this._item.icon.icon_name = 'screen-shared-symbolic';
        this.menu.addMenuItem(this._item);
    }

    _sync() {
        if (this._handles.size == 0) {
            this._indicator.visible = false;
            this._item.actor.visible = false;
        } else {
            this._indicator.visible = true;
            this._item.actor.visible = true;
        }
    }

    _onStopped(handle) {
        this._handles.delete(handle);
        this._sync();
    }

    _onNewHandle(handle) {
        this._handles.add(handle);
        handle.connect('stopped', this._onStopped.bind(this));

        if (this._handles.size == 1) {
            this._ensureControls();
            this._sync();
        }
    }
};
(uuay)params.js1// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

// parse:
// @params: caller-provided parameter object, or %null
// @defaults-provided defaults object
// @allowExtras: whether or not to allow properties not in @default
//
// Examines @params and fills in default values from @defaults for
// any properties in @defaults that don't appear in @params. If
// @allowExtras is not %true, it will throw an error if @params
// contains any properties that aren't in @defaults.
//
// If @params is %null, this returns the values from @defaults.
//
// Return value: a new object, containing the merged parameters from
// @params and @defaults
function parse(params, defaults, allowExtras) {
    let ret = {}, prop;

    if (!params)
        params = {};

    for (prop in params) {
        if (!(prop in defaults) && !allowExtras)
            throw new Error('Unrecognized parameter "' + prop + '"');
        ret[prop] = params[prop];
    }

    for (prop in defaults) {
        if (!(prop in params))
            ret[prop] = defaults[prop];
    }

    return ret;
}(uuay)core.js7"// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const System = imports.system;

const Main = imports.ui.main;
const Scripting = imports.ui.scripting;

// This performance script measure the most important (core) performance
// metrics for the shell. By looking at the output metrics of this script
// someone should be able to get an idea of how well the shell is performing
// on a particular system.

var METRICS = {
    overviewLatencyFirst:
    { description: "Time to first frame after triggering overview, first time",
      units: "us" },
    overviewFpsFirst:
    { description: "Frame rate when going to the overview, first time",
      units: "frames / s" },
    overviewLatencySubsequent:
    { description: "Time to first frame after triggering overview, second time",
      units: "us"},
    overviewFpsSubsequent:
    { description: "Frames rate when going to the overview, second time",
      units: "frames / s" },
    overviewFps5Windows:
    { description: "Frames rate when going to the overview, 5 windows open",
      units: "frames / s" },
    overviewFps10Windows:
    { description: "Frames rate when going to the overview, 10 windows open",
      units: "frames / s" },
    overviewFps5Maximized:
    { description: "Frames rate when going to the overview, 5 maximized windows open",
      units: "frames / s" },
    overviewFps10Maximized:
    { description: "Frames rate when going to the overview, 10 maximized windows open",
      units: "frames / s" },
    overviewFps5Alpha:
    { description: "Frames rate when going to the overview, 5 alpha-transparent windows open",
      units: "frames / s" },
    overviewFps10Alpha:
    { description: "Frames rate when going to the overview, 10 alpha-transparent windows open",
      units: "frames / s" },
    usedAfterOverview:
    { description: "Malloc'ed bytes after the overview is shown once",
      units: "B" },
    leakedAfterOverview:
    { description: "Additional malloc'ed bytes the second time the overview is shown",
      units: "B" },
    applicationsShowTimeFirst:
    { description: "Time to switch to applications view, first time",
      units: "us" },
    applicationsShowTimeSubsequent:
    { description: "Time to switch to applications view, second time",
      units: "us"}
};

let WINDOW_CONFIGS = [
    { width: 640, height: 480, alpha: false, maximized: false, count: 1,  metric: 'overviewFpsSubsequent' },
    { width: 640, height: 480, alpha: false, maximized: false, count: 5,  metric: 'overviewFps5Windows'  },
    { width: 640, height: 480, alpha: false, maximized: false, count: 10, metric: 'overviewFps10Windows'  },
    { width: 640, height: 480, alpha: false, maximized: true,  count: 5,  metric: 'overviewFps5Maximized' },
    { width: 640, height: 480, alpha: false, maximized: true,  count: 10, metric: 'overviewFps10Maximized' },
    { width: 640, height: 480, alpha: true,  maximized: false, count: 5,  metric: 'overviewFps5Alpha' },
    { width: 640, height: 480, alpha: true,  maximized: false, count: 10, metric: 'overviewFps10Alpha' }
];

function *run() {
    Scripting.defineScriptEvent("overviewShowStart", "Starting to show the overview");
    Scripting.defineScriptEvent("overviewShowDone", "Overview finished showing");
    Scripting.defineScriptEvent("afterShowHide", "After a show/hide cycle for the overview");
    Scripting.defineScriptEvent("applicationsShowStart", "Starting to switch to applications view");
    Scripting.defineScriptEvent("applicationsShowDone", "Done switching to applications view");

    // Enable recording of timestamps for different points in the frame cycle
    global.frame_timestamps = true;

    Main.overview.connect('shown', () => {
        Scripting.scriptEvent('overviewShowDone');
    });

    yield Scripting.sleep(1000);

    for (let i = 0; i < 2 * WINDOW_CONFIGS.length; i++) {
        // We go to the overview twice for each configuration; the first time
        // to calculate the mipmaps for the windows, the second time to get
        // a clean set of numbers.
        if ((i % 2) == 0) {
            let config = WINDOW_CONFIGS[i / 2];
            yield Scripting.destroyTestWindows();

            for (let k = 0; k < config.count; k++)
                yield Scripting.createTestWindow({ width: config.width,
                                                   height: config.height,
                                                   alpha: config.alpha,
                                                   maximized: config.maximized });

            yield Scripting.waitTestWindows();
            yield Scripting.sleep(1000);
            yield Scripting.waitLeisure();
        }

        Scripting.scriptEvent('overviewShowStart');
        Main.overview.show();

        yield Scripting.waitLeisure();
        Main.overview.hide();
        yield Scripting.waitLeisure();

        System.gc();
        yield Scripting.sleep(1000);
        Scripting.collectStatistics();
        Scripting.scriptEvent('afterShowHide');
    }

    yield Scripting.destroyTestWindows();
    yield Scripting.sleep(1000);

    Main.overview.show();
    yield Scripting.waitLeisure();

    for (let i = 0; i < 2; i++) {
        Scripting.scriptEvent('applicationsShowStart');
        Main.overview._dash.showAppsButton.checked = true;
        yield Scripting.waitLeisure();
        Scripting.scriptEvent('applicationsShowDone');
        Main.overview._dash.showAppsButton.checked = false;
        yield Scripting.waitLeisure();
    }
}

let showingOverview = false;
let finishedShowingOverview = false;
let overviewShowStart;
let overviewFrames;
let overviewLatency;
let mallocUsedSize = 0;
let overviewShowCount = 0;
let firstOverviewUsedSize;
let haveSwapComplete = false;
let applicationsShowStart;
let applicationsShowCount = 0;

function script_overviewShowStart(time) {
    showingOverview = true;
    finishedShowingOverview = false;
    overviewShowStart = time;
    overviewFrames = 0;
}

function script_overviewShowDone(time) {
    // We've set up the state at the end of the zoom out, but we
    // need to wait for one more frame to paint before we count
    // ourselves as done.
    finishedShowingOverview = true;
}

function script_applicationsShowStart(time) {
    applicationsShowStart = time;
}

function script_applicationsShowDone(time) {
    applicationsShowCount++;
    if (applicationsShowCount == 1)
        METRICS.applicationsShowTimeFirst.value = time - applicationsShowStart;
    else
        METRICS.applicationsShowTimeSubsequent.value = time - applicationsShowStart;
}

function script_afterShowHide(time) {
    if (overviewShowCount == 1) {
        METRICS.usedAfterOverview.value = mallocUsedSize;
    } else {
        METRICS.leakedAfterOverview.value = mallocUsedSize - METRICS.usedAfterOverview.value;
    }
}

function malloc_usedSize(time, bytes) {
    mallocUsedSize = bytes;
}

function _frameDone(time) {
    if (showingOverview) {
        if (overviewFrames == 0)
            overviewLatency = time - overviewShowStart;

        overviewFrames++;
    }

    if (finishedShowingOverview) {
        showingOverview = false;
        finishedShowingOverview = false;
        overviewShowCount++;

        let dt = (time - (overviewShowStart + overviewLatency)) / 1000000;

        // If we see a start frame and an end frame, that would
        // be 1 frame for a FPS computation, hence the '- 1'
        let fps = (overviewFrames - 1) / dt;

        if (overviewShowCount == 1) {
            METRICS.overviewLatencyFirst.value = overviewLatency;
            METRICS.overviewFpsFirst.value = fps;
        } else if (overviewShowCount == 2) {
            METRICS.overviewLatencySubsequent.value = overviewLatency;
        }

        // Other than overviewFpsFirst, we collect FPS metrics the second
        // we show each window configuration. overviewShowCount is 1,2,3...
        if (overviewShowCount % 2 == 0) {
            let config = WINDOW_CONFIGS[(overviewShowCount / 2) - 1];
            METRICS[config.metric].value = fps;
        }
    }
}

function glx_swapComplete(time, swapTime) {
    haveSwapComplete = true;

    _frameDone(swapTime);
}

function clutter_stagePaintDone(time) {
    // If we aren't receiving GLXBufferSwapComplete events, then we approximate
    // the time the user sees a frame with the time we finished doing drawing
    // commands for the frame. This doesn't take into account the time for
    // the GPU to finish painting, and the time for waiting for the buffer
    // swap, but if this are uniform - every frame takes the same time to draw -
    // then it won't upset our FPS calculation, though the latency value
    // will be slightly too low.

    if (!haveSwapComplete)
        _frameDone(time);
}
(uuay)scripting.jsS0// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib, Meta, Shell } = imports.gi;
const Mainloop = imports.mainloop;

const Config = imports.misc.config;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Util = imports.misc.util;

const { loadInterfaceXML } = imports.misc.fileUtils;

// This module provides functionality for driving the shell user interface
// in an automated fashion. The primary current use case for this is
// automated performance testing (see runPerfScript()), but it could
// be applied to other forms of automation, such as testing for
// correctness as well.
//
// When scripting an automated test we want to make a series of calls
// in a linear fashion, but we also want to be able to let the main
// loop run so actions can finish. For this reason we write the script
// as a generator function that yields when it want to let the main
// loop run.
//
//    yield Scripting.sleep(1000);
//    main.overview.show();
//    yield Scripting.waitLeisure();
//
// While it isn't important to the person writing the script, the actual
// yielded result is a function that the caller uses to provide the
// callback for resuming the script.

/**
 * sleep:
 * @milliseconds: number of milliseconds to wait
 *
 * Used within an automation script to pause the the execution of the
 * current script for the specified amount of time. Use as
 * 'yield Scripting.sleep(500);'
 */
function sleep(milliseconds) {
    return new Promise(resolve => {
        let id = Mainloop.timeout_add(milliseconds, () => {
            resolve();
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(id, '[gnome-shell] sleep');
    });
}

/**
 * waitLeisure:
 *
 * Used within an automation script to pause the the execution of the
 * current script until the shell is completely idle. Use as
 * 'yield Scripting.waitLeisure();'
 */
function waitLeisure() {
    return new Promise(resolve => {
        global.run_at_leisure(resolve);
    });
}

const PerfHelperIface = loadInterfaceXML('org.gnome.Shell.PerfHelper');
var PerfHelperProxy = Gio.DBusProxy.makeProxyWrapper(PerfHelperIface);
function PerfHelper() {
    return new PerfHelperProxy(Gio.DBus.session, 'org.gnome.Shell.PerfHelper', '/org/gnome/Shell/PerfHelper');
}

let _perfHelper = null;
function _getPerfHelper() {
    if (_perfHelper == null)
        _perfHelper = new PerfHelper();

    return _perfHelper;
}

function _spawnPerfHelper() {
    let path = Config.LIBEXECDIR;
    let command = `${path}/gnome-shell-perf-helper`;
    Util.trySpawnCommandLine(command);
}

function _callRemote(obj, method, ...args) {
    return new Promise((resolve, reject) => {
        args.push((result, excp) => {
            if (excp)
                reject(excp);
            else
                resolve();
        });

        method.apply(obj, args);
    });
}

/**
 * createTestWindow:
 * @params: options for window creation.
 *   width - width of window, in pixels (default 640)
 *   height - height of window, in pixels (default 480)
 *   alpha - whether the window should have an alpha channel (default false)
 *   maximized - whether the window should be created maximized (default false)
 *   redraws - whether the window should continually redraw itself (default false)
 * @maximized: whethe the window should be created maximized
 *
 * Creates a window using gnome-shell-perf-helper for testing purposes.
 * While this function can be used with yield in an automation
 * script to pause until the D-Bus call to the helper process returns,
 * because of the normal X asynchronous mapping process, to actually wait
 * until the window has been mapped and exposed, use waitTestWindows().
 */
function createTestWindow(params) {
    params = Params.parse(params, { width: 640,
                                    height: 480,
                                    alpha: false,
                                    maximized: false,
                                    redraws: false });

    let perfHelper = _getPerfHelper();
    return _callRemote(perfHelper, perfHelper.CreateWindowRemote,
                       params.width, params.height,
                       params.alpha, params.maximized, params.redraws);
}

/**
 * waitTestWindows:
 *
 * Used within an automation script to pause until all windows previously
 * created with createTestWindow have been mapped and exposed.
 */
function waitTestWindows() {
    let perfHelper = _getPerfHelper();
    return _callRemote(perfHelper, perfHelper.WaitWindowsRemote);
}

/**
 * destroyTestWindows:
 *
 * Destroys all windows previously created with createTestWindow().
 * While this function can be used with yield in an automation
 * script to pause until the D-Bus call to the helper process returns,
 * this doesn't guarantee that Mutter has actually finished the destroy
 * process because of normal X asynchronicity.
 */
function destroyTestWindows() {
    let perfHelper = _getPerfHelper();
    return _callRemote(perfHelper, perfHelper.DestroyWindowsRemote);
}

/**
 * defineScriptEvent
 * @name: The event will be called script.<name>
 * @description: Short human-readable description of the event
 *
 * Convenience function to define a zero-argument performance event
 * within the 'script' namespace that is reserved for events defined locally
 * within a performance automation script
 */
function defineScriptEvent(name, description) {
    Shell.PerfLog.get_default().define_event("script." + name,
                                             description,
                                             "");
}

/**
 * scriptEvent
 * @name: Name registered with defineScriptEvent()
 *
 * Convenience function to record a script-local performance event
 * previously defined with defineScriptEvent
 */
function scriptEvent(name) {
    Shell.PerfLog.get_default().event("script." + name);
}

/**
 * collectStatistics
 *
 * Convenience function to trigger statistics collection
 */
function collectStatistics() {
    Shell.PerfLog.get_default().collect_statistics();
}

function _collect(scriptModule, outputFile) {
    let eventHandlers = {};

    for (let f in scriptModule) {
        let m = /([A-Za-z]+)_([A-Za-z]+)/.exec(f);
        if (m)
            eventHandlers[m[1] + "." + m[2]] = scriptModule[f];
    }

    Shell.PerfLog.get_default().replay(
        (time, eventName, signature, arg) => {
            if (eventName in eventHandlers)
                eventHandlers[eventName](time, arg);
        });

    if ('finish' in scriptModule)
        scriptModule.finish();

    if (outputFile) {
        let f = Gio.file_new_for_path(outputFile);
        let raw = f.replace(null, false,
                            Gio.FileCreateFlags.NONE,
                            null);
        let out = Gio.BufferedOutputStream.new_sized (raw, 4096);
        Shell.write_string_to_stream (out, "{\n");

        Shell.write_string_to_stream(out, '"events":\n');
        Shell.PerfLog.get_default().dump_events(out);

        let monitors = Main.layoutManager.monitors;
        let primary = Main.layoutManager.primaryIndex;
        Shell.write_string_to_stream(out, ',\n"monitors":\n[');
        for (let i = 0; i < monitors.length; i++) {
            let monitor = monitors[i];
            if (i != 0)
                Shell.write_string_to_stream(out, ', ');
            Shell.write_string_to_stream(out, '"%s%dx%d+%d+%d"'.format(i == primary ? "*" : "",
                                                                       monitor.width, monitor.height,
                                                                       monitor.x, monitor.y));
        }
        Shell.write_string_to_stream(out, ' ]');

        Shell.write_string_to_stream(out, ',\n"metrics":\n[ ');
        let first = true;
        for (let name in scriptModule.METRICS) {
            let metric = scriptModule.METRICS[name];
            // Extra checks here because JSON.stringify generates
            // invalid JSON for undefined values
            if (metric.description == null) {
                log("Error: No description found for metric " + name);
                continue;
            }
            if (metric.units == null) {
                log("Error: No units found for metric " + name);
                continue;
            }
            if (metric.value == null) {
                log("Error: No value found for metric " + name);
                continue;
            }

            if (!first)
                Shell.write_string_to_stream(out, ',\n  ');
            first = false;

            Shell.write_string_to_stream(out,
                                         '{ "name": ' + JSON.stringify(name) + ',\n' +
                                         '    "description": ' + JSON.stringify(metric.description) + ',\n' +
                                         '    "units": ' + JSON.stringify(metric.units) + ',\n' +
                                         '    "value": ' + JSON.stringify(metric.value) + ' }');
        }
        Shell.write_string_to_stream(out, ' ]');

        Shell.write_string_to_stream (out, ',\n"log":\n');
        Shell.PerfLog.get_default().dump_log(out);

        Shell.write_string_to_stream (out, '\n}\n');
        out.close(null);
    } else {
        let metrics = [];
        for (let metric in scriptModule.METRICS)
            metrics.push(metric);

        metrics.sort();

        print ('------------------------------------------------------------');
        for (let i = 0; i < metrics.length; i++) {
            let metric = metrics[i];
            print ('# ' + scriptModule.METRICS[metric].description);
            print (metric + ': ' +  scriptModule.METRICS[metric].value + scriptModule.METRICS[metric].units);
        }
        print ('------------------------------------------------------------');
    }
}

async function _runPerfScript(scriptModule, outputFile) {
    for (let step of scriptModule.run()) {
        try {
            await step; // eslint-disable-line no-await-in-loop
        } catch (err) {
            log(`Script failed: ${err}\n${err.stack}`);
            Meta.exit(Meta.ExitCode.ERROR);
        }
    }

    try {
        _collect(scriptModule, outputFile);
    } catch (err) {
        log(`Script failed: ${err}\n${err.stack}`);
        Meta.exit(Meta.ExitCode.ERROR);
    }
    Meta.exit(Meta.ExitCode.SUCCESS);
}

/**
 * runPerfScript
 * @scriptModule: module object with run and finish functions
 *    and event handlers
 *
 * Runs a script for automated collection of performance data. The
 * script is defined as a Javascript module with specified contents.
 *
 * First the run() function within the module will be called as a
 * generator to automate a series of actions. These actions will
 * trigger performance events and the script can also record its
 * own performance events.
 *
 * Then the recorded event log is replayed using handler functions
 * within the module. The handler for the event 'foo.bar' is called
 * foo_bar().
 *
 * Finally if the module has a function called finish(), that will
 * be called.
 *
 * The event handler and finish functions are expected to fill in
 * metrics to an object within the module called METRICS. Each
 * property of this object represents an individual metric. The
 * name of the property is the name of the metric, the value
 * of the property is an object with the following properties:
 *
 *  description: human readable description of the metric
 *  units: a string representing the units of the metric. It has
 *   the form '<unit> <unit> ... / <unit> / <unit> ...'. Certain
 *   unit values are recognized: s, ms, us, B, KiB, MiB. Other
 *   values can appear but are uninterpreted. Examples 's',
 *   '/ s', 'frames', 'frames / s', 'MiB / s / frame'
 *  value: computed value of the metric
 *
 * The resulting metrics will be written to @outputFile as JSON, or,
 * if @outputFile is not provided, logged.
 *
 * After running the script and collecting statistics from the
 * event log, GNOME Shell will exit.
 **/
function runPerfScript(scriptModule, outputFile) {
    Shell.PerfLog.get_default().set_enabled(true);
    _spawnPerfHelper();

    Gio.bus_watch_name(Gio.BusType.SESSION,
        'org.gnome.Shell.PerfHelper',
        Gio.BusNameWatcherFlags.NONE,
        () => _runPerfScript(scriptModule, outputFile),
        null);
}
(uuay)autorunManager.js*// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, St } = imports.gi;

const GnomeSession = imports.misc.gnomeSession;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

const { loadInterfaceXML } = imports.misc.fileUtils;

// GSettings keys
const SETTINGS_SCHEMA = 'org.gnome.desktop.media-handling';
const SETTING_DISABLE_AUTORUN = 'autorun-never';
const SETTING_START_APP = 'autorun-x-content-start-app';
const SETTING_IGNORE = 'autorun-x-content-ignore';
const SETTING_OPEN_FOLDER = 'autorun-x-content-open-folder';

var AutorunSetting = {
    RUN: 0,
    IGNORE: 1,
    FILES: 2,
    ASK: 3
};

// misc utils
function shouldAutorunMount(mount) {
    let root = mount.get_root();
    let volume = mount.get_volume();

    if (!volume || !volume.allowAutorun)
        return false;

    if (root.is_native() && isMountRootHidden(root))
        return false;

    return true;
}

function isMountRootHidden(root) {
    let path = root.get_path();

    // skip any mounts in hidden directory hierarchies
    return (path.indexOf('/.') != -1);
}

function isMountNonLocal(mount) {
    // If the mount doesn't have an associated volume, that means it's
    // an uninteresting filesystem. Most devices that we care about will
    // have a mount, like media players and USB sticks.
    let volume = mount.get_volume();
    if (volume == null)
        return true;

    return (volume.get_identifier("class") == "network");
}

function startAppForMount(app, mount) {
    let files = [];
    let root = mount.get_root();
    let retval = false;

    files.push(root);

    try {
        retval = app.launch(files, 
                            global.create_app_launch_context(0, -1));
    } catch (e) {
        log('Unable to launch the application ' + app.get_name()
            + ': ' + e.toString());
    }

    return retval;
}

/******************************************/

const HotplugSnifferIface = loadInterfaceXML('org.gnome.Shell.HotplugSniffer');
const HotplugSnifferProxy = Gio.DBusProxy.makeProxyWrapper(HotplugSnifferIface);
function HotplugSniffer() {
    return new HotplugSnifferProxy(Gio.DBus.session,
                                   'org.gnome.Shell.HotplugSniffer',
                                   '/org/gnome/Shell/HotplugSniffer');
}

var ContentTypeDiscoverer = class {
    constructor(callback) {
        this._callback = callback;
        this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
    }

    guessContentTypes(mount) {
        let autorunEnabled = !this._settings.get_boolean(SETTING_DISABLE_AUTORUN);
        let shouldScan = autorunEnabled && !isMountNonLocal(mount);

        if (shouldScan) {
            // guess mount's content types using GIO
            mount.guess_content_type(false, null,
                                     this._onContentTypeGuessed.bind(this));
        } else {
            this._emitCallback(mount, []);
        }
    }

    _onContentTypeGuessed(mount, res) {
        let contentTypes = [];

        try {
            contentTypes = mount.guess_content_type_finish(res);
        } catch (e) {
            log('Unable to guess content types on added mount ' + mount.get_name()
                + ': ' + e.toString());
        }

        if (contentTypes.length) {
            this._emitCallback(mount, contentTypes);
        } else {
            let root = mount.get_root();

            let hotplugSniffer = new HotplugSniffer();
            hotplugSniffer.SniffURIRemote(root.get_uri(),
                 ([contentTypes]) => {
                     this._emitCallback(mount, contentTypes);
                 });
        }
    }

    _emitCallback(mount, contentTypes) {
        if (!contentTypes)
            contentTypes = [];

        // we're not interested in win32 software content types here
        contentTypes = contentTypes.filter(
            type => (type != 'x-content/win32-software')
        );

        let apps = [];
        contentTypes.forEach(type => {
            let app = Gio.app_info_get_default_for_type(type, false);

            if (app)
                apps.push(app);
        });

        if (apps.length == 0)
            apps.push(Gio.app_info_get_default_for_type('inode/directory', false));

        this._callback(mount, apps, contentTypes);
    }
};

var AutorunManager = class {
    constructor() {
        this._session = new GnomeSession.SessionManager();
        this._volumeMonitor = Gio.VolumeMonitor.get();

        this._dispatcher = new AutorunDispatcher(this);
    }

    enable() {
        this._mountAddedId = this._volumeMonitor.connect('mount-added', this._onMountAdded.bind(this));
        this._mountRemovedId = this._volumeMonitor.connect('mount-removed', this._onMountRemoved.bind(this));
    }

    disable() {
        this._volumeMonitor.disconnect(this._mountAddedId);
        this._volumeMonitor.disconnect(this._mountRemovedId);
    }

    _onMountAdded(monitor, mount) {
        // don't do anything if our session is not the currently
        // active one
        if (!this._session.SessionIsActive)
            return;

        let discoverer = new ContentTypeDiscoverer((mount, apps, contentTypes) => {
            this._dispatcher.addMount(mount, apps, contentTypes);
        });
        discoverer.guessContentTypes(mount);
    }

    _onMountRemoved(monitor, mount) {
        this._dispatcher.removeMount(mount);
    }
};

var AutorunDispatcher = class {
    constructor(manager) {
        this._manager = manager;
        this._sources = [];
        this._settings = new Gio.Settings({ schema_id: SETTINGS_SCHEMA });
    }

    _getAutorunSettingForType(contentType) {
        let runApp = this._settings.get_strv(SETTING_START_APP);
        if (runApp.indexOf(contentType) != -1)
            return AutorunSetting.RUN;

        let ignore = this._settings.get_strv(SETTING_IGNORE);
        if (ignore.indexOf(contentType) != -1)
            return AutorunSetting.IGNORE;

        let openFiles = this._settings.get_strv(SETTING_OPEN_FOLDER);
        if (openFiles.indexOf(contentType) != -1)
            return AutorunSetting.FILES;

        return AutorunSetting.ASK;
    }

    _getSourceForMount(mount) {
        let filtered = this._sources.filter(source => (source.mount == mount));

        // we always make sure not to add two sources for the same
        // mount in addMount(), so it's safe to assume filtered.length
        // is always either 1 or 0.
        if (filtered.length == 1)
            return filtered[0];

        return null;
    }

    _addSource(mount, apps) {
        // if we already have a source showing for this 
        // mount, return
        if (this._getSourceForMount(mount))
            return;
     
        // add a new source
        this._sources.push(new AutorunSource(this._manager, mount, apps));
    }

    addMount(mount, apps, contentTypes) {
        // if autorun is disabled globally, return
        if (this._settings.get_boolean(SETTING_DISABLE_AUTORUN))
            return;

        // if the mount doesn't want to be autorun, return
        if (!shouldAutorunMount(mount))
            return;

        let setting;
        if (contentTypes.length > 0)
            setting = this._getAutorunSettingForType(contentTypes[0]);
        else
            setting = AutorunSetting.ASK;

        // check at the settings for the first content type
        // to see whether we should ask
        if (setting == AutorunSetting.IGNORE)
            return; // return right away

        let success = false;
        let app = null;

        if (setting == AutorunSetting.RUN) {
            app = Gio.app_info_get_default_for_type(contentTypes[0], false);
        } else if (setting == AutorunSetting.FILES) {
            app = Gio.app_info_get_default_for_type('inode/directory', false);
        }

        if (app)
            success = startAppForMount(app, mount);

        // we fallback here also in case the settings did not specify 'ask',
        // but we failed launching the default app or the default file manager
        if (!success)
            this._addSource(mount, apps);
    }

    removeMount(mount) {
        let source = this._getSourceForMount(mount);
        
        // if we aren't tracking this mount, don't do anything
        if (!source)
            return;

        // destroy the notification source
        source.destroy();
    }
};

var AutorunSource = class extends MessageTray.Source {
    constructor(manager, mount, apps) {
        super(mount.get_name());

        this._manager = manager;
        this.mount = mount;
        this.apps = apps;

        this._notification = new AutorunNotification(this._manager, this);

        // add ourselves as a source, and popup the notification
        Main.messageTray.add(this);
        this.notify(this._notification);
    }

    getIcon() {
        return this.mount.get_icon();
    }

    _createPolicy() {
        return new MessageTray.NotificationApplicationPolicy('org.gnome.Nautilus');
    }
};

var AutorunNotification = class extends MessageTray.Notification {
    constructor(manager, source) {
        super(source, source.title);

        this._manager = manager;
        this._mount = source.mount;
    }

    createBanner() {
        let banner = new MessageTray.NotificationBanner(this);

        this.source.apps.forEach(app => {
            let actor = this._buttonForApp(app);

            if (actor)
                banner.addButton(actor);
        });

        return banner;
    }

    _buttonForApp(app) {
        let box = new St.BoxLayout();
        let icon = new St.Icon({ gicon: app.get_icon(),
                                 style_class: 'hotplug-notification-item-icon' });
        box.add(icon);

        let label = new St.Bin({ y_align: St.Align.MIDDLE,
                                 child: new St.Label
                                 ({ text: _("Open with %s").format(app.get_name()) })
                               });
        box.add(label);

        let button = new St.Button({ child: box,
                                     x_fill: true,
                                     x_align: St.Align.START,
                                     x_expand: true,
                                     button_mask: St.ButtonMask.ONE,
                                     style_class: 'hotplug-notification-item button' });

        button.connect('clicked', () => {
            startAppForMount(app, this._mount);
            this.destroy();
        });

        return button;
    }

    activate() {
        super.activate();

        let app = Gio.app_info_get_default_for_type('inode/directory', false);
        startAppForMount(app, this._mount);
    }
};

var Component = AutorunManager;
(uuay)inhibitShortcutsDialog.js�const { Clutter, Gio, GLib, GObject, Gtk, Meta, Shell } = imports.gi;

const Dialog = imports.ui.dialog;
const ModalDialog = imports.ui.modalDialog;
const PermissionStore = imports.misc.permissionStore;

const WAYLAND_KEYBINDINGS_SCHEMA = 'org.gnome.mutter.wayland.keybindings';

const APP_WHITELIST = ['gnome-control-center.desktop'];
const APP_PERMISSIONS_TABLE = 'gnome';
const APP_PERMISSIONS_ID = 'shortcuts-inhibitor';
const GRANTED = 'GRANTED';
const DENIED = 'DENIED';

var DialogResponse = Meta.InhibitShortcutsDialogResponse;

var InhibitShortcutsDialog = GObject.registerClass({
    Implements: [Meta.InhibitShortcutsDialog],
    Properties: {
        'window': GObject.ParamSpec.override('window', Meta.InhibitShortcutsDialog)
    }
}, class InhibitShortcutsDialog extends GObject.Object {
    _init(window) {
        super._init();
        this._window = window;

        this._dialog = new ModalDialog.ModalDialog();
        this._buildLayout();
    }

    get window() {
        return this._window;
    }

    set window(window) {
        this._window = window;
    }

    get _app() {
        let windowTracker = Shell.WindowTracker.get_default();
        return windowTracker.get_window_app(this._window);
    }

    _getRestoreAccel() {
        let settings = new Gio.Settings({ schema_id: WAYLAND_KEYBINDINGS_SCHEMA });
        let accel = settings.get_strv('restore-shortcuts')[0] || '';
        return Gtk.accelerator_get_label.apply(null,
                                               Gtk.accelerator_parse(accel));
    }

    _shouldUsePermStore() {
        return this._app && !this._app.is_window_backed();
    }

    _saveToPermissionStore(grant) {
        if (!this._shouldUsePermStore() || this._permStore == null)
            return;

        let permissions = {};
        permissions[this._app.get_id()] = [grant];
        let data = GLib.Variant.new('av', {});

        this._permStore.SetRemote(APP_PERMISSIONS_TABLE,
                                  true,
                                  APP_PERMISSIONS_ID,
                                  permissions,
                                  data,
            (result, error) => {
                if (error != null)
                    log(error.message);
            });
    }

    _buildLayout() {
        let name = this._app ? this._app.get_name() : this._window.title;

        /* Translators: %s is an application name like "Settings" */
        let title = name ? _("%s wants to inhibit shortcuts").format(name)
                         : _("Application wants to inhibit shortcuts");
        let icon = new Gio.ThemedIcon({ name: 'dialog-warning-symbolic' });

        let contentParams = { icon, title };

        let restoreAccel = this._getRestoreAccel();
        if (restoreAccel)
            contentParams.subtitle =
                /* Translators: %s is a keyboard shortcut like "Super+x" */
                _("You can restore shortcuts by pressing %s.").format(restoreAccel);

        let content = new Dialog.MessageDialogContent(contentParams);
        this._dialog.contentLayout.add_actor(content);

        this._dialog.addButton({ label: _("Deny"),
                                 action: () => {
                                     this._saveToPermissionStore(DENIED);
                                     this._emitResponse(DialogResponse.DENY);
                                 },
                                 key: Clutter.KEY_Escape });

        this._dialog.addButton({ label: _("Allow"),
                                 action: () => {
                                     this._saveToPermissionStore(GRANTED);
                                     this._emitResponse(DialogResponse.ALLOW);
                                 },
                                 default: true });
    }

    _emitResponse(response) {
        this.emit('response', response);
        this._dialog.close();
    }

    vfunc_show() {
        if (this._app && APP_WHITELIST.indexOf(this._app.get_id()) != -1) {
            this._emitResponse(DialogResponse.ALLOW);
            return;
        }

        if (!this._shouldUsePermStore()) {
            this._dialog.open();
            return;
        }

        /* Check with the permission store */
        let appId = this._app.get_id();
        this._permStore = new PermissionStore.PermissionStore((proxy, error) => {
            if (error) {
                log(error.message);
                this._dialog.open();
                return;
            }

            this._permStore.LookupRemote(APP_PERMISSIONS_TABLE,
                                         APP_PERMISSIONS_ID,
                (res, error) => {
                    if (error) {
                        this._dialog.open();
                        log(error.message);
                        return;
                    }

                    let [permissions, data] = res;
                    if (permissions[appId] === undefined) // Not found
                        this._dialog.open();
                    else if (permissions[appId] == GRANTED)
                        this._emitResponse(DialogResponse.ALLOW);
                    else
                        this._emitResponse(DialogResponse.DENY);
                });
        });
    }

    vfunc_hide() {
        this._dialog.close();
    }
});
(uuay)modalDialog.js� // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Atk, Clutter, Shell, St } = imports.gi;
const Signals = imports.signals;

const Dialog = imports.ui.dialog;
const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

var OPEN_AND_CLOSE_TIME = 0.1;
var FADE_OUT_DIALOG_TIME = 1.0;

var State = {
    OPENED: 0,
    CLOSED: 1,
    OPENING: 2,
    CLOSING: 3,
    FADED_OUT: 4
};

var ModalDialog = class {
    constructor(params) {
        params = Params.parse(params, { shellReactive: false,
                                        styleClass: null,
                                        actionMode: Shell.ActionMode.SYSTEM_MODAL,
                                        shouldFadeIn: true,
                                        shouldFadeOut: true,
                                        destroyOnClose: true });

        this.state = State.CLOSED;
        this._hasModal = false;
        this._actionMode = params.actionMode;
        this._shellReactive = params.shellReactive;
        this._shouldFadeIn = params.shouldFadeIn;
        this._shouldFadeOut = params.shouldFadeOut;
        this._destroyOnClose = params.destroyOnClose;

        this._group = new St.Widget({ visible: false,
                                      x: 0,
                                      y: 0,
                                      accessible_role: Atk.Role.DIALOG });
        Main.layoutManager.modalDialogGroup.add_actor(this._group);

        let constraint = new Clutter.BindConstraint({ source: global.stage,
                                                      coordinate: Clutter.BindCoordinate.ALL });
        this._group.add_constraint(constraint);

        this._group.connect('destroy', this._onGroupDestroy.bind(this));

        this.backgroundStack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        this._backgroundBin = new St.Bin({ child: this.backgroundStack,
                                           x_fill: true, y_fill: true });
        this._monitorConstraint = new Layout.MonitorConstraint();
        this._backgroundBin.add_constraint(this._monitorConstraint);
        this._group.add_actor(this._backgroundBin);

        this.dialogLayout = new Dialog.Dialog(this.backgroundStack, params.styleClass);
        this.contentLayout = this.dialogLayout.contentLayout;
        this.buttonLayout = this.dialogLayout.buttonLayout;

        if (!this._shellReactive) {
            this._lightbox = new Lightbox.Lightbox(this._group,
                                                   { inhibitEvents: true,
                                                     radialEffect: true });
            this._lightbox.highlight(this._backgroundBin);

            this._eventBlocker = new Clutter.Actor({ reactive: true });
            this.backgroundStack.add_actor(this._eventBlocker);
        }

        global.focus_manager.add_group(this.dialogLayout);
        this._initialKeyFocus = null;
        this._initialKeyFocusDestroyId = 0;
        this._savedKeyFocus = null;
    }

    destroy() {
        this._group.destroy();
    }

    clearButtons() {
        this.dialogLayout.clearButtons();
    }

    setButtons(buttons) {
        this.clearButtons();

        for (let buttonInfo of buttons)
            this.addButton(buttonInfo);
    }

    addButton(buttonInfo) {
        return this.dialogLayout.addButton(buttonInfo);
    }

    _onGroupDestroy() {
        this.emit('destroy');
    }

    _fadeOpen(onPrimary) {
        if (onPrimary)
            this._monitorConstraint.primary = true;
        else
            this._monitorConstraint.index = global.display.get_current_monitor();

        this.state = State.OPENING;

        this.dialogLayout.opacity = 255;
        if (this._lightbox)
            this._lightbox.show();
        this._group.opacity = 0;
        this._group.show();
        Tweener.addTween(this._group,
                         { opacity: 255,
                           time: this._shouldFadeIn ? OPEN_AND_CLOSE_TIME : 0,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this.state = State.OPENED;
                               this.emit('opened');
                           }
                         });
    }

    setInitialKeyFocus(actor) {
        if (this._initialKeyFocusDestroyId)
            this._initialKeyFocus.disconnect(this._initialKeyFocusDestroyId);

        this._initialKeyFocus = actor;

        this._initialKeyFocusDestroyId = actor.connect('destroy', () => {
            this._initialKeyFocus = null;
            this._initialKeyFocusDestroyId = 0;
        });
    }

    open(timestamp, onPrimary) {
        if (this.state == State.OPENED || this.state == State.OPENING)
            return true;

        if (!this.pushModal(timestamp))
            return false;

        this._fadeOpen(onPrimary);
        return true;
    }

    _closeComplete() {
        this.state = State.CLOSED;
        this._group.hide();
        this.emit('closed');

        if (this._destroyOnClose)
            this.destroy();
    }

    close(timestamp) {
        if (this.state == State.CLOSED || this.state == State.CLOSING)
            return;

        this.state = State.CLOSING;
        this.popModal(timestamp);
        this._savedKeyFocus = null;

        if (this._shouldFadeOut)
            Tweener.addTween(this._group,
                             { opacity: 0,
                               time: OPEN_AND_CLOSE_TIME,
                               transition: 'easeOutQuad',
                               onComplete: this._closeComplete.bind(this)
                             })
        else
            this._closeComplete();
    }

    // Drop modal status without closing the dialog; this makes the
    // dialog insensitive as well, so it needs to be followed shortly
    // by either a close() or a pushModal()
    popModal(timestamp) {
        if (!this._hasModal)
            return;

        let focus = global.stage.key_focus;
        if (focus && this._group.contains(focus))
            this._savedKeyFocus = focus;
        else
            this._savedKeyFocus = null;
        Main.popModal(this._group, timestamp);
        this._hasModal = false;

        if (!this._shellReactive)
            this._eventBlocker.raise_top();
    }

    pushModal(timestamp) {
        if (this._hasModal)
            return true;

        let params = { actionMode: this._actionMode };
        if (timestamp)
            params['timestamp'] = timestamp;
        if (!Main.pushModal(this._group, params))
            return false;

        this._hasModal = true;
        if (this._savedKeyFocus) {
            this._savedKeyFocus.grab_key_focus();
            this._savedKeyFocus = null;
        } else {
            let focus = this._initialKeyFocus || this.dialogLayout.initialKeyFocus;
            focus.grab_key_focus();
        }

        if (!this._shellReactive)
            this._eventBlocker.lower_bottom();
        return true;
    }

    // This method is like close, but fades the dialog out much slower,
    // and leaves the lightbox in place. Once in the faded out state,
    // the dialog can be brought back by an open call, or the lightbox
    // can be dismissed by a close call.
    //
    // The main point of this method is to give some indication to the user
    // that the dialog reponse has been acknowledged but will take a few
    // moments before being processed.
    // e.g., if a user clicked "Log Out" then the dialog should go away
    // imediately, but the lightbox should remain until the logout is
    // complete.
    _fadeOutDialog(timestamp) {
        if (this.state == State.CLOSED || this.state == State.CLOSING)
            return;

        if (this.state == State.FADED_OUT)
            return;

        this.popModal(timestamp);
        Tweener.addTween(this.dialogLayout,
                         { opacity: 0,
                           time:    FADE_OUT_DIALOG_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                               this.state = State.FADED_OUT;
                           }
                         });
    }
};
Signals.addSignalMethods(ModalDialog.prototype);
(uuay)keyboardManager.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GLib, GnomeDesktop, Meta } = imports.gi;

const Main = imports.ui.main;

var DEFAULT_LOCALE = 'en_US';
var DEFAULT_LAYOUT = 'us';
var DEFAULT_VARIANT = '';

let _xkbInfo = null;

function getXkbInfo() {
    if (_xkbInfo == null)
        _xkbInfo = new GnomeDesktop.XkbInfo();
    return _xkbInfo;
}

let _keyboardManager = null;

function getKeyboardManager() {
    if (_keyboardManager == null)
        _keyboardManager = new KeyboardManager();
    return _keyboardManager;
}

function releaseKeyboard() {
    if (Main.modalCount > 0)
        global.display.unfreeze_keyboard(global.get_current_time());
    else
        global.display.ungrab_keyboard(global.get_current_time());
}

function holdKeyboard() {
    global.display.freeze_keyboard(global.get_current_time());
}

var KeyboardManager = class {
    constructor() {
        // The XKB protocol doesn't allow for more that 4 layouts in a
        // keymap. Wayland doesn't impose this limit and libxkbcommon can
        // handle up to 32 layouts but since we need to support X clients
        // even as a Wayland compositor, we can't bump this.
        this.MAX_LAYOUTS_PER_GROUP = 4;

        this._xkbInfo = getXkbInfo();
        this._current = null;
        this._localeLayoutInfo = this._getLocaleLayout();
        this._layoutInfos = {};
        this._currentKeymap = null;
    }

    _applyLayoutGroup(group) {
        let options = this._buildOptionsString();
        let [layouts, variants] = this._buildGroupStrings(group);

        if (this._currentKeymap &&
            this._currentKeymap.layouts == layouts &&
            this._currentKeymap.variants == variants &&
            this._currentKeymap.options == options)
            return;

        this._currentKeymap = {layouts, variants, options};
        Meta.get_backend().set_keymap(layouts, variants, options);
    }

    _applyLayoutGroupIndex(idx) {
        Meta.get_backend().lock_layout_group(idx);
    }

    apply(id) {
        let info = this._layoutInfos[id];
        if (!info)
            return;

        if (this._current && this._current.group == info.group) {
            if (this._current.groupIndex != info.groupIndex)
                this._applyLayoutGroupIndex(info.groupIndex);
        } else {
            this._applyLayoutGroup(info.group);
            this._applyLayoutGroupIndex(info.groupIndex);
        }

        this._current = info;
    }

    reapply() {
        if (!this._current)
            return;

        this._applyLayoutGroup(this._current.group);
        this._applyLayoutGroupIndex(this._current.groupIndex);
    }

    setUserLayouts(ids) {
        this._current = null;
        this._layoutInfos = {};

        for (let i = 0; i < ids.length; ++i) {
            let [found, , , _layout, _variant] = this._xkbInfo.get_layout_info(ids[i]);
            if (found)
                this._layoutInfos[ids[i]] = { id: ids[i], layout: _layout, variant: _variant };
        }

        let i = 0;
        let group = [];
        for (let id in this._layoutInfos) {
            // We need to leave one slot on each group free so that we
            // can add a layout containing the symbols for the
            // language used in UI strings to ensure that toolkits can
            // handle mnemonics like Alt+Ф even if the user is
            // actually typing in a different layout.
            let groupIndex = i % (this.MAX_LAYOUTS_PER_GROUP - 1);
            if (groupIndex == 0)
                group = [];

            let info = this._layoutInfos[id];
            group[groupIndex] = info;
            info.group = group;
            info.groupIndex = groupIndex;

            i += 1;
        }
    }

    _getLocaleLayout() {
        let locale = GLib.get_language_names()[0];
        if (locale.indexOf('_') == -1)
            locale = DEFAULT_LOCALE;

        let [found, , id] = GnomeDesktop.get_input_source_from_locale(locale);
        if (!found)
            [, , id] = GnomeDesktop.get_input_source_from_locale(DEFAULT_LOCALE);

        let _layout, _variant;
        [found, , , _layout, _variant] = this._xkbInfo.get_layout_info(id);
        if (found)
            return { layout: _layout, variant: _variant };
        else
            return { layout: DEFAULT_LAYOUT, variant: DEFAULT_VARIANT };
    }

    _buildGroupStrings(_group) {
        let group = _group.concat(this._localeLayoutInfo);
        let layouts = group.map(g => g.layout).join(',');
        let variants = group.map(g => g.variant).join(',');
        return [layouts, variants];
    }

    setKeyboardOptions(options) {
        this._xkbOptions = options;
    }

    _buildOptionsString() {
        let options = this._xkbOptions.join(',');
        return options;
    }
};
(uuay)brightness.js�	// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, St } = imports.gi;

const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const Slider = imports.ui.slider;

const { loadInterfaceXML } = imports.misc.fileUtils;

const BUS_NAME = 'org.gnome.SettingsDaemon.Power';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Power';

const BrightnessInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Power.Screen');
const BrightnessProxy = Gio.DBusProxy.makeProxyWrapper(BrightnessInterface);

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super('display-brightness-symbolic');
        this._proxy = new BrightnessProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
                                          (proxy, error) => {
                                              if (error) {
                                                  log(error.message);
                                                  return;
                                              }

                                              this._proxy.connect('g-properties-changed', this._sync.bind(this));
                                              this._sync();
                                          });

        this._item = new PopupMenu.PopupBaseMenuItem({ activate: false });
        this.menu.addMenuItem(this._item);

        this._slider = new Slider.Slider(0);
        this._slider.connect('value-changed', this._sliderChanged.bind(this));
        this._slider.actor.accessible_name = _("Brightness");

        let icon = new St.Icon({ icon_name: 'display-brightness-symbolic',
                                 style_class: 'popup-menu-icon' });
        this._item.actor.add(icon);
        this._item.actor.add(this._slider.actor, { expand: true });
        this._item.actor.connect('button-press-event', (actor, event) => {
            return this._slider.startDragging(event);
        });
        this._item.actor.connect('key-press-event', (actor, event) => {
            return this._slider.onKeyPressEvent(actor, event);
        });

    }

    _sliderChanged(slider, value) {
        let percent = value * 100;
        this._proxy.Brightness = percent;
    }

    _sync() {
        let visible = this._proxy.Brightness >= 0;
        this._item.actor.visible = visible;
        if (visible)
            this._slider.setValue(this._proxy.Brightness / 100.0);
    }
};
(uuay)network.js1// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
const { Clutter, Gio, GLib, GObject, Meta, NM, Polkit, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const Animation = imports.ui.animation;
const Config = imports.misc.config;
const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;
const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog;
const ModemManager = imports.misc.modemManager;
const Rfkill = imports.ui.status.rfkill;
const Util = imports.misc.util;

const { loadInterfaceXML } = imports.misc.fileUtils;

Gio._promisify(Gio.DBusConnection.prototype, 'call', 'call_finish');

const NMConnectionCategory = {
    INVALID: 'invalid',
    WIRED: 'wired',
    WIRELESS: 'wireless',
    WWAN: 'wwan',
    VPN: 'vpn'
};

const NMAccessPointSecurity = {
    NONE: 1,
    WEP: 2,
    WPA_PSK: 3,
    WPA2_PSK: 4,
    WPA_ENT: 5,
    WPA2_ENT: 6
};

var MAX_DEVICE_ITEMS = 4;

// small optimization, to avoid using [] all the time
const NM80211Mode = NM['80211Mode'];
const NM80211ApFlags = NM['80211ApFlags'];
const NM80211ApSecurityFlags = NM['80211ApSecurityFlags'];

var PortalHelperResult = {
    CANCELLED: 0,
    COMPLETED: 1,
    RECHECK: 2
};

const PortalHelperIface = loadInterfaceXML('org.gnome.Shell.PortalHelper');
const PortalHelperProxy = Gio.DBusProxy.makeProxyWrapper(PortalHelperIface);

function signalToIcon(value) {
    if (value > 80)
        return 'excellent';
    if (value > 55)
        return 'good';
    if (value > 30)
        return 'ok';
    if (value > 5)
        return 'weak';
    return 'none';
}

function ssidToLabel(ssid) {
    let label;
    if (ssid)
        label = NM.utils_ssid_to_utf8(ssid.get_data());
    if (!label)
        label = _("<unknown>");
    return label;
}

function ensureActiveConnectionProps(active, client) {
    if (!active._primaryDevice) {
        // This list is guaranteed to have only one device in it.
        let device = active.get_devices()[0]._delegate;
        active._primaryDevice = device;
    }
}

function launchSettingsPanel(panel, ...args) {
    const param = new GLib.Variant('(sav)',
        [panel, args.map(s => new GLib.Variant('s', s))]);
    const platformData = {
        'desktop-startup-id': new GLib.Variant('s',
            '_TIME%s'.format(global.get_current_time())),
    };
    try {
        Gio.DBus.session.call(
            'org.gnome.ControlCenter',
            '/org/gnome/ControlCenter',
            'org.freedesktop.Application',
            'ActivateAction',
            new GLib.Variant('(sava{sv})',
                ['launch-panel', [param], platformData]),
            null,
            Gio.DBusCallFlags.NONE,
            -1,
            null);
    } catch (e) {
        log('Failed to launch Settings panel: %s'.format(e.message));
    }
}

var NMConnectionItem = class {
    constructor(section, connection) {
        this._section = section;
        this._connection = connection;
        this._activeConnection = null;
        this._activeConnectionChangedId = 0;

        this._buildUI();
        this._sync();
    }

    _buildUI() {
        this.labelItem = new PopupMenu.PopupMenuItem('');
        this.labelItem.connect('activate', this._toggle.bind(this));

        this.radioItem = new PopupMenu.PopupMenuItem(this._connection.get_id(), false);
        this.radioItem.connect('activate', this._activate.bind(this));
    }

    destroy() {
        this.labelItem.destroy();
        this.radioItem.destroy();
    }

    updateForConnection(connection) {
        // connection should always be the same object
        // (and object path) as this._connection, but
        // this can be false if NetworkManager was restarted
        // and picked up connections in a different order
        // Just to be safe, we set it here again

        this._connection = connection;
        this.radioItem.label.text = connection.get_id();
        this._sync();
        this.emit('name-changed');
    }

    getName() {
        return this._connection.get_id();
    }

    isActive() {
        if (this._activeConnection == null)
            return false;

        return this._activeConnection.state <= NM.ActiveConnectionState.ACTIVATED;
    }

    _sync() {
        let isActive = this.isActive();
        this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel();
        this.radioItem.setOrnament(isActive ? PopupMenu.Ornament.DOT : PopupMenu.Ornament.NONE);
        this.emit('icon-changed');
    }

    _toggle() {
        if (this._activeConnection == null)
            this._section.activateConnection(this._connection);
        else
            this._section.deactivateConnection(this._activeConnection);

        this._sync();
    }

    _activate() {
        if (this._activeConnection == null)
            this._section.activateConnection(this._connection);

        this._sync();
    }

    _connectionStateChanged(ac, newstate, reason) {
        this._sync();
    }

    setActiveConnection(activeConnection) {
        if (this._activeConnectionChangedId > 0) {
            this._activeConnection.disconnect(this._activeConnectionChangedId);
            this._activeConnectionChangedId = 0;
        }

        this._activeConnection = activeConnection;

        if (this._activeConnection)
            this._activeConnectionChangedId = this._activeConnection.connect('notify::state',
                                                                             this._connectionStateChanged.bind(this));

        this._sync();
    }
};
Signals.addSignalMethods(NMConnectionItem.prototype);

var NMConnectionSection = class {
    constructor(client) {
        if (new.target === NMConnectionSection)
            throw new TypeError('Cannot instantiate abstract type ' + new.target.name);

        this._client = client;

        this._connectionItems = new Map();
        this._connections = [];

        this._labelSection = new PopupMenu.PopupMenuSection();
        this._radioSection = new PopupMenu.PopupMenuSection();

        this.item = new PopupMenu.PopupSubMenuMenuItem('', true);
        this.item.menu.addMenuItem(this._labelSection);
        this.item.menu.addMenuItem(this._radioSection);

        this._notifyConnectivityId = this._client.connect('notify::connectivity', this._iconChanged.bind(this));
    }

    destroy() {
        if (this._notifyConnectivityId != 0) {
            this._client.disconnect(this._notifyConnectivityId);
            this._notifyConnectivityId = 0;
        }

        this.item.destroy();
    }

    _iconChanged() {
        this._sync();
        this.emit('icon-changed');
    }

    _sync() {
        let nItems = this._connectionItems.size;

        this._radioSection.actor.visible = (nItems > 1);
        this._labelSection.actor.visible = (nItems == 1);

        this.item.label.text = this._getStatus();
        this.item.icon.icon_name = this._getMenuIcon();
    }

    _getMenuIcon() {
        return this.getIndicatorIcon();
    }

    getConnectLabel() {
        return _("Connect");
    }

    _connectionValid(connection) {
        return true;
    }

    _connectionSortFunction(one, two) {
        return GLib.utf8_collate(one.get_id(), two.get_id());
    }

    _makeConnectionItem(connection) {
        return new NMConnectionItem(this, connection);
    }

    checkConnection(connection) {
        if (!this._connectionValid(connection))
            return;

        // This function is called everytime connection is added or updated
        // In the usual case, we already added this connection and UUID
        // didn't change. So we need to check if we already have an item,
        // and update it for properties in the connection that changed
        // (the only one we care about is the name)
        // But it's also possible we didn't know about this connection
        // (eg, during coldplug, or because it was updated and suddenly
        // it's valid for this device), in which case we add a new item

        let item = this._connectionItems.get(connection.get_uuid());
        if (item)
            this._updateForConnection(item, connection);
        else
            this._addConnection(connection);
    }

    _updateForConnection(item, connection) {
        let pos = this._connections.indexOf(connection);

        this._connections.splice(pos, 1);
        pos = Util.insertSorted(this._connections, connection, this._connectionSortFunction.bind(this));
        this._labelSection.moveMenuItem(item.labelItem, pos);
        this._radioSection.moveMenuItem(item.radioItem, pos);

        item.updateForConnection(connection);
    }

    _addConnection(connection) {
        let item = this._makeConnectionItem(connection);
        if (!item)
            return;

        item.connect('icon-changed', () => { this._iconChanged(); });
        item.connect('activation-failed', (item, reason) => {
            this.emit('activation-failed', reason);
        });
        item.connect('name-changed', this._sync.bind(this));

        let pos = Util.insertSorted(this._connections, connection, this._connectionSortFunction.bind(this));
        this._labelSection.addMenuItem(item.labelItem, pos);
        this._radioSection.addMenuItem(item.radioItem, pos);
        this._connectionItems.set(connection.get_uuid(), item);
        this._sync();
    }

    removeConnection(connection) {
        let uuid = connection.get_uuid();
        let item = this._connectionItems.get(uuid);
        if (item == undefined)
            return;

        item.destroy();
        this._connectionItems.delete(uuid);

        let pos = this._connections.indexOf(connection);
        this._connections.splice(pos, 1);

        this._sync();
    }
};
Signals.addSignalMethods(NMConnectionSection.prototype);

var NMConnectionDevice = class extends NMConnectionSection {
    constructor(client, device) {
        if (new.target === NMConnectionDevice)
            throw new TypeError('Cannot instantiate abstract type ' + new.target.name);

        super(client);
        this._device = device;
        this._description = '';

        this._autoConnectItem = this.item.menu.addAction(_("Connect"), this._autoConnect.bind(this));
        this._deactivateItem = this._radioSection.addAction(_("Turn Off"), this.deactivateConnection.bind(this));

        this._stateChangedId = this._device.connect('state-changed', this._deviceStateChanged.bind(this));
        this._activeConnectionChangedId = this._device.connect('notify::active-connection', this._activeConnectionChanged.bind(this));
    }

    _canReachInternet() {
        if (this._client.primary_connection != this._device.active_connection)
            return true;

        return this._client.connectivity == NM.ConnectivityState.FULL;
    }

    _autoConnect() {
        let connection = new NM.SimpleConnection();
        this._client.add_and_activate_connection_async(connection, this._device, null, null, null);
    }

    destroy() {
        if (this._stateChangedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId);
            this._stateChangedId = 0;
        }
        if (this._activeConnectionChangedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._activeConnectionChangedId);
            this._activeConnectionChangedId = 0;
        }

        super.destroy();
    }

    _activeConnectionChanged() {
        if (this._activeConnection) {
            let item = this._connectionItems.get(this._activeConnection.connection.get_uuid());
            item.setActiveConnection(null);
            this._activeConnection = null;
        }

        this._sync();
    }

    _deviceStateChanged(device, newstate, oldstate, reason) {
        if (newstate == oldstate) {
            log('device emitted state-changed without actually changing state');
            return;
        }

        /* Emit a notification if activation fails, but don't do it
           if the reason is no secrets, as that indicates the user
           cancelled the agent dialog */
        if (newstate == NM.DeviceState.FAILED &&
            reason != NM.DeviceStateReason.NO_SECRETS) {
            this.emit('activation-failed', reason);
        }

        this._sync();
    }

    _connectionValid(connection) {
        return this._device.connection_valid(connection);
    }

    activateConnection(connection) {
        this._client.activate_connection_async(connection, this._device, null, null, null);
    }

    deactivateConnection(activeConnection) {
        this._device.disconnect(null);
    }

    setDeviceDescription(desc) {
        this._description = desc;
        this._sync();
    }

    _getDescription() {
        return this._description;
    }

    _sync() {
        let nItems = this._connectionItems.size;
        this._autoConnectItem.actor.visible = (nItems == 0);
        this._deactivateItem.actor.visible = this._device.state > NM.DeviceState.DISCONNECTED;

        if (this._activeConnection == null) {
            let activeConnection = this._device.active_connection;
            if (activeConnection && activeConnection.connection) {
                let item = this._connectionItems.get(activeConnection.connection.get_uuid());
                if (item) {
                    this._activeConnection = activeConnection;
                    ensureActiveConnectionProps(this._activeConnection, this._client);
                    item.setActiveConnection(this._activeConnection);
                }
            }
        }

        super._sync();
    }

    _getStatus() {
        if (!this._device)
            return '';

        switch(this._device.state) {
        case NM.DeviceState.DISCONNECTED:
            /* Translators: %s is a network identifier */
            return _("%s Off").format(this._getDescription());
        case NM.DeviceState.ACTIVATED:
            /* Translators: %s is a network identifier */
            return _("%s Connected").format(this._getDescription());
        case NM.DeviceState.UNMANAGED:
            /* Translators: this is for network devices that are physically present but are not
               under NetworkManager's control (and thus cannot be used in the menu);
               %s is a network identifier */
            return _("%s Unmanaged").format(this._getDescription());
        case NM.DeviceState.DEACTIVATING:
            /* Translators: %s is a network identifier */
            return _("%s Disconnecting").format(this._getDescription());
        case NM.DeviceState.PREPARE:
        case NM.DeviceState.CONFIG:
        case NM.DeviceState.IP_CONFIG:
        case NM.DeviceState.IP_CHECK:
        case NM.DeviceState.SECONDARIES:
            /* Translators: %s is a network identifier */
            return _("%s Connecting").format(this._getDescription());
        case NM.DeviceState.NEED_AUTH:
            /* Translators: this is for network connections that require some kind of key or password; %s is a network identifier */
            return _("%s Requires Authentication").format(this._getDescription());
        case NM.DeviceState.UNAVAILABLE:
            // This state is actually a compound of various states (generically unavailable,
            // firmware missing), that are exposed by different properties (whose state may
            // or may not updated when we receive state-changed).
            if (this._device.firmware_missing) {
                /* Translators: this is for devices that require some kind of firmware or kernel
                   module, which is missing; %s is a network identifier */
                return _("Firmware Missing For %s").format(this._getDescription());
            }
            /* Translators: this is for a network device that cannot be activated (for example it
               is disabled by rfkill, or it has no coverage; %s is a network identifier */
            return _("%s Unavailable").format(this._getDescription());
        case NM.DeviceState.FAILED:
            /* Translators: %s is a network identifier */
            return _("%s Connection Failed").format(this._getDescription());
        default:
            log('Device state invalid, is %d'.format(this._device.state));
            return 'invalid';
        }
    }
};

var NMDeviceWired = class extends NMConnectionDevice {
    constructor(client, device) {
        super(client, device);

        this.item.menu.addSettingsAction(_("Wired Settings"), 'gnome-network-panel.desktop');
    }

    get category() {
        return NMConnectionCategory.WIRED;
    }

    _hasCarrier() {
        if (this._device instanceof NM.DeviceEthernet)
            return this._device.carrier;
        else
            return true;
    }

    _sync() {
        this.item.actor.visible = this._hasCarrier();
        super._sync();
    }

    getIndicatorIcon() {
        if (this._device.active_connection) {
            let state = this._device.active_connection.state;

            if (state == NM.ActiveConnectionState.ACTIVATING) {
                return 'network-wired-acquiring-symbolic';
            } else if (state == NM.ActiveConnectionState.ACTIVATED) {
                if (this._canReachInternet())
                    return 'network-wired-symbolic';
                else
                    return 'network-wired-no-route-symbolic';
            } else {
                return 'network-wired-disconnected-symbolic';
            }
        } else
            return 'network-wired-disconnected-symbolic';
    }
};

var NMDeviceModem = class extends NMConnectionDevice {
    constructor(client, device) {
        super(client, device);

        this.item.menu.addSettingsAction(_("Mobile Broadband Settings"), 'gnome-network-panel.desktop');

        this._mobileDevice = null;

        let capabilities = device.current_capabilities;
        if (device.udi.indexOf('/org/freedesktop/ModemManager1/Modem') == 0)
            this._mobileDevice = new ModemManager.BroadbandModem(device.udi, capabilities);
        else if (capabilities & NM.DeviceModemCapabilities.GSM_UMTS)
            this._mobileDevice = new ModemManager.ModemGsm(device.udi);
        else if (capabilities & NM.DeviceModemCapabilities.CDMA_EVDO)
            this._mobileDevice = new ModemManager.ModemCdma(device.udi);
        else if (capabilities & NM.DeviceModemCapabilities.LTE)
            this._mobileDevice = new ModemManager.ModemGsm(device.udi);

        if (this._mobileDevice) {
            this._operatorNameId = this._mobileDevice.connect('notify::operator-name', this._sync.bind(this));
            this._signalQualityId = this._mobileDevice.connect('notify::signal-quality', () => {
                this._iconChanged();
            });
        }

        this._sessionUpdatedId =
            Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    get category() {
        return NMConnectionCategory.WWAN;
    }

    _autoConnect() {
        launchSettingsPanel('network', 'connect-3g', this._device.get_path());
    }

    _sessionUpdated() {
        this._autoConnectItem.sensitive = Main.sessionMode.hasWindows;
    }

    destroy() {
        if (this._operatorNameId) {
            this._mobileDevice.disconnect(this._operatorNameId);
            this._operatorNameId = 0;
        }
        if (this._signalQualityId) {
            this._mobileDevice.disconnect(this._signalQualityId);
            this._signalQualityId = 0;
        }
        if (this._sessionUpdatedId) {
            Main.sessionMode.disconnect(this._sessionUpdatedId);
            this._sessionUpdatedId = 0;
        }

        super.destroy();
    }

    _getStatus() {
        if (!this._client.wwan_hardware_enabled)
            /* Translators: %s is a network identifier */
            return _("%s Hardware Disabled").format(this._getDescription());
        else if (!this._client.wwan_enabled)
            /* Translators: this is for a network device that cannot be activated
               because it's disabled by rfkill (airplane mode); %s is a network identifier */
            return _("%s Disabled").format(this._getDescription());
        else if (this._device.state == NM.DeviceState.ACTIVATED &&
                 this._mobileDevice && this._mobileDevice.operator_name)
            return this._mobileDevice.operator_name;
        else
            return super._getStatus();
    }

    getIndicatorIcon() {
        if (this._device.active_connection) {
            if (this._device.active_connection.state == NM.ActiveConnectionState.ACTIVATING)
                return 'network-cellular-acquiring-symbolic';

            return this._getSignalIcon();
        } else {
            return 'network-cellular-signal-none-symbolic';
        }
    }

    _getSignalIcon() {
        return 'network-cellular-signal-' + signalToIcon(this._mobileDevice.signal_quality) + '-symbolic';
    }
};

var NMDeviceBluetooth = class extends NMConnectionDevice {
    constructor(client, device) {
        super(client, device);

        this.item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-network-panel.desktop');
    }

    get category() {
        return NMConnectionCategory.WWAN;
    }

    _getDescription() {
        return this._device.name;
    }

    getConnectLabel() {
        return _("Connect to Internet");
    }

    getIndicatorIcon() {
        if (this._device.active_connection) {
            let state = this._device.active_connection.state;
            if (state == NM.ActiveConnectionState.ACTIVATING)
                return 'network-cellular-acquiring-symbolic';
            else if (state == NM.ActiveConnectionState.ACTIVATED)
                return 'network-cellular-connected-symbolic';
            else
                return 'network-cellular-signal-none-symbolic';
        } else {
            return 'network-cellular-signal-none-symbolic';
        }
    }
};

var NMWirelessDialogItem = class {
    constructor(network) {
        this._network = network;
        this._ap = network.accessPoints[0];

        this.actor = new St.BoxLayout({ style_class: 'nm-dialog-item',
                                        can_focus: true,
                                        reactive: true });
        this.actor.connect('key-focus-in', () => { this.emit('selected'); });
        let action = new Clutter.ClickAction();
        action.connect('clicked', () => { this.actor.grab_key_focus(); });
        this.actor.add_action(action);

        let title = ssidToLabel(this._ap.get_ssid());
        this._label = new St.Label({ text: title });

        this.actor.label_actor = this._label;
        this.actor.add(this._label, { x_align: St.Align.START });

        this._selectedIcon = new St.Icon({ style_class: 'nm-dialog-icon',
                                           icon_name: 'object-select-symbolic' });
        this.actor.add(this._selectedIcon);

        this._icons = new St.BoxLayout({ style_class: 'nm-dialog-icons' });
        this.actor.add(this._icons, { expand: true, x_fill: false, x_align: St.Align.END });

        this._secureIcon = new St.Icon({ style_class: 'nm-dialog-icon' });
        if (this._ap._secType != NMAccessPointSecurity.NONE)
            this._secureIcon.icon_name = 'network-wireless-encrypted-symbolic';
        this._icons.add_actor(this._secureIcon);

        this._signalIcon = new St.Icon({ style_class: 'nm-dialog-icon' });
        this._icons.add_actor(this._signalIcon);

        this._sync();
    }

    _sync() {
        this._signalIcon.icon_name = this._getSignalIcon();
    }

    updateBestAP(ap) {
        this._ap = ap;
        this._sync();
    }

    setActive(isActive) {
        this._selectedIcon.opacity = isActive ? 255 : 0;
    }

    _getSignalIcon() {
        if (this._ap.mode == NM80211Mode.ADHOC)
            return 'network-workgroup-symbolic';
        else
            return 'network-wireless-signal-' + signalToIcon(this._ap.strength) + '-symbolic';
    }
};
Signals.addSignalMethods(NMWirelessDialogItem.prototype);

var NMWirelessDialog = class extends ModalDialog.ModalDialog {
    constructor(client, device) {
        super({ styleClass: 'nm-dialog' });

        this._client = client;
        this._device = device;

        this._wirelessEnabledChangedId = this._client.connect('notify::wireless-enabled',
                                                              this._syncView.bind(this));

        this._rfkill = Rfkill.getRfkillManager();
        this._airplaneModeChangedId = this._rfkill.connect('airplane-mode-changed',
                                                           this._syncView.bind(this));

        this._networks = [];
        this._buildLayout();

        let connections = client.get_connections();
        this._connections = connections.filter(
            connection => device.connection_valid(connection)
        );

        this._apAddedId = device.connect('access-point-added', this._accessPointAdded.bind(this));
        this._apRemovedId = device.connect('access-point-removed', this._accessPointRemoved.bind(this));
        this._activeApChangedId = device.connect('notify::active-access-point', this._activeApChanged.bind(this));

        // accessPointAdded will also create dialog items
        let accessPoints = device.get_access_points() || [ ];
        accessPoints.forEach(ap => {
            this._accessPointAdded(this._device, ap);
        });

        this._selectedNetwork = null;
        this._activeApChanged();
        this._updateSensitivity();
        this._syncView();

        this._scanTimeoutId = Mainloop.timeout_add_seconds(15, this._onScanTimeout.bind(this));
        GLib.Source.set_name_by_id(this._scanTimeoutId, '[gnome-shell] this._onScanTimeout');
        this._onScanTimeout();

        let id = Main.sessionMode.connect('updated', () => {
            if (Main.sessionMode.allowSettings)
                return;

            Main.sessionMode.disconnect(id);
            this.close();
        });
    }

    destroy() {
        if (this._apAddedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._apAddedId);
            this._apAddedId = 0;
        }
        if (this._apRemovedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._apRemovedId);
            this._apRemovedId = 0;
        }
        if (this._activeApChangedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._activeApChangedId);
            this._activeApChangedId = 0;
        }
        if (this._wirelessEnabledChangedId) {
            this._client.disconnect(this._wirelessEnabledChangedId);
            this._wirelessEnabledChangedId = 0;
        }
        if (this._airplaneModeChangedId) {
            this._rfkill.disconnect(this._airplaneModeChangedId);
            this._airplaneModeChangedId = 0;
        }

        if (this._scanTimeoutId) {
            Mainloop.source_remove(this._scanTimeoutId);
            this._scanTimeoutId = 0;
        }

        if (this._syncVisibilityId) {
            Meta.later_remove(this._syncVisibilityId);
            this._syncVisibilityId = 0;
        }

        super.destroy();
    }

    _onScanTimeout() {
        this._device.request_scan_async(null, null);
        return GLib.SOURCE_CONTINUE;
    }

    _activeApChanged() {
        if (this._activeNetwork)
            this._activeNetwork.item.setActive(false);

        this._activeNetwork = null;
        if (this._device.active_access_point) {
            let idx = this._findNetwork(this._device.active_access_point);
            if (idx >= 0)
                this._activeNetwork = this._networks[idx];
        }

        if (this._activeNetwork)
            this._activeNetwork.item.setActive(true);
        this._updateSensitivity();
    }

    _updateSensitivity() {
        let connectSensitive = this._client.wireless_enabled && this._selectedNetwork && (this._selectedNetwork != this._activeNetwork);
        this._connectButton.reactive = connectSensitive;
        this._connectButton.can_focus = connectSensitive;
    }

    _syncView() {
        if (this._rfkill.airplaneMode) {
            this._airplaneBox.show();

            this._airplaneIcon.icon_name = 'airplane-mode-symbolic';
            this._airplaneHeadline.text = _("Airplane Mode is On");
            this._airplaneText.text = _("Wi-Fi is disabled when airplane mode is on.");
            this._airplaneButton.label = _("Turn Off Airplane Mode");

            this._airplaneButton.visible = !this._rfkill.hwAirplaneMode;
            this._airplaneInactive.visible = this._rfkill.hwAirplaneMode;
            this._noNetworksBox.hide();
        } else if (!this._client.wireless_enabled) {
            this._airplaneBox.show();

            this._airplaneIcon.icon_name = 'dialog-information-symbolic';
            this._airplaneHeadline.text = _("Wi-Fi is Off");
            this._airplaneText.text = _("Wi-Fi needs to be turned on in order to connect to a network.");
            this._airplaneButton.label = _("Turn On Wi-Fi");

            this._airplaneButton.show();
            this._airplaneInactive.hide();
            this._noNetworksBox.hide();
        } else {
            this._airplaneBox.hide();

            this._noNetworksBox.visible = (this._networks.length == 0);
        }

        if (this._noNetworksBox.visible)
            this._noNetworksSpinner.play();
        else
            this._noNetworksSpinner.stop();
    }

    _buildLayout() {
        let headline = new St.BoxLayout({ style_class: 'nm-dialog-header-hbox' });

        let icon = new St.Icon({ style_class: 'nm-dialog-header-icon',
                                 icon_name: 'network-wireless-signal-excellent-symbolic' });

        let titleBox = new St.BoxLayout({ vertical: true });
        let title = new St.Label({ style_class: 'nm-dialog-header',
                                   text: _("Wi-Fi Networks") });
        let subtitle = new St.Label({ style_class: 'nm-dialog-subheader',
                                      text: _("Select a network") });
        titleBox.add(title);
        titleBox.add(subtitle);

        headline.add(icon);
        headline.add(titleBox);

        this.contentLayout.style_class = 'nm-dialog-content';
        this.contentLayout.add(headline);

        this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });

        this._itemBox = new St.BoxLayout({ vertical: true });
        this._scrollView = new St.ScrollView({ style_class: 'nm-dialog-scroll-view' });
        this._scrollView.set_x_expand(true);
        this._scrollView.set_y_expand(true);
        this._scrollView.set_policy(St.PolicyType.NEVER,
                                    St.PolicyType.AUTOMATIC);
        this._scrollView.add_actor(this._itemBox);
        this._stack.add_child(this._scrollView);

        this._noNetworksBox = new St.BoxLayout({ vertical: true,
                                                 style_class: 'no-networks-box',
                                                 x_align: Clutter.ActorAlign.CENTER,
                                                 y_align: Clutter.ActorAlign.CENTER });

        this._noNetworksSpinner = new Animation.Spinner(16);
        this._noNetworksBox.add_actor(this._noNetworksSpinner.actor);
        this._noNetworksBox.add_actor(new St.Label({ style_class: 'no-networks-label',
                                                     text: _("No Networks") }));
        this._stack.add_child(this._noNetworksBox);

        this._airplaneBox = new St.BoxLayout({ vertical: true,
                                               style_class: 'nm-dialog-airplane-box',
                                               x_align: Clutter.ActorAlign.CENTER,
                                               y_align: Clutter.ActorAlign.CENTER });
        this._airplaneIcon = new St.Icon({ icon_size: 48 });
        this._airplaneHeadline = new St.Label({ style_class: 'nm-dialog-airplane-headline headline' });
        this._airplaneText = new St.Label({ style_class: 'nm-dialog-airplane-text' });

        let airplaneSubStack = new St.Widget({ layout_manager: new Clutter.BinLayout });
        this._airplaneButton = new St.Button({ style_class: 'modal-dialog-button button' });
        this._airplaneButton.connect('clicked', () => {
            if (this._rfkill.airplaneMode)
                this._rfkill.airplaneMode = false;
            else
                this._client.wireless_enabled = true;
        });
        airplaneSubStack.add_actor(this._airplaneButton);
        this._airplaneInactive = new St.Label({ style_class: 'nm-dialog-airplane-text',
                                                text: _("Use hardware switch to turn off") });
        airplaneSubStack.add_actor(this._airplaneInactive);

        this._airplaneBox.add(this._airplaneIcon, { x_align: St.Align.MIDDLE });
        this._airplaneBox.add(this._airplaneHeadline, { x_align: St.Align.MIDDLE });
        this._airplaneBox.add(this._airplaneText, { x_align: St.Align.MIDDLE });
        this._airplaneBox.add(airplaneSubStack, { x_align: St.Align.MIDDLE });
        this._stack.add_child(this._airplaneBox);

        this.contentLayout.add(this._stack, { expand: true });

        this._disconnectButton = this.addButton({ action: this.close.bind(this),
                                                  label: _("Cancel"),
                                                  key: Clutter.Escape });
        this._connectButton = this.addButton({ action: this._connect.bind(this),
                                               label: _("Connect"),
                                               key: Clutter.Return });
    }

    _connect() {
        let network = this._selectedNetwork;
        if (network.connections.length > 0) {
            let connection = network.connections[0];
            this._client.activate_connection_async(connection, this._device, null, null, null);
        } else {
            let accessPoints = network.accessPoints;
            if ((accessPoints[0]._secType == NMAccessPointSecurity.WPA2_ENT)
                || (accessPoints[0]._secType == NMAccessPointSecurity.WPA_ENT)) {
                // 802.1x-enabled APs require further configuration, so they're
                // handled in gnome-control-center
                launchSettingsPanel('wifi', 'connect-8021x-wifi',
                    this._getDeviceDBusPath(), accessPoints[0].get_path());
            } else {
                let connection = new NM.SimpleConnection();
                this._client.add_and_activate_connection_async(connection, this._device, accessPoints[0].get_path(), null, null)
            }
        }

        this.close();
    }

    _getDeviceDBusPath() {
        // nm_object_get_path() is shadowed by nm_device_get_path()
        return NM.Object.prototype.get_path.call(this._device);
    }

    _notifySsidCb(accessPoint) {
        if (accessPoint.get_ssid() != null) {
            accessPoint.disconnect(accessPoint._notifySsidId);
            accessPoint._notifySsidId = 0;
            this._accessPointAdded(this._device, accessPoint);
        }
    }

    _getApSecurityType(accessPoint) {
        if (accessPoint._secType)
            return accessPoint._secType;

        let flags = accessPoint.flags;
        let wpa_flags = accessPoint.wpa_flags;
        let rsn_flags = accessPoint.rsn_flags;
        let type;
        if (rsn_flags != NM80211ApSecurityFlags.NONE) {
            /* RSN check first so that WPA+WPA2 APs are treated as RSN/WPA2 */
            if (rsn_flags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
	        type = NMAccessPointSecurity.WPA2_ENT;
	    else if (rsn_flags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
	        type = NMAccessPointSecurity.WPA2_PSK;
        } else if (wpa_flags != NM80211ApSecurityFlags.NONE) {
            if (wpa_flags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
                type = NMAccessPointSecurity.WPA_ENT;
            else if (wpa_flags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
	        type = NMAccessPointSecurity.WPA_PSK;
        } else {
            if (flags & NM80211ApFlags.PRIVACY)
                type = NMAccessPointSecurity.WEP;
            else
                type = NMAccessPointSecurity.NONE;
        }

        // cache the found value to avoid checking flags all the time
        accessPoint._secType = type;
        return type;
    }

    _networkSortFunction(one, two) {
        let oneHasConnection = one.connections.length != 0;
        let twoHasConnection = two.connections.length != 0;

        // place known connections first
        // (-1 = good order, 1 = wrong order)
        if (oneHasConnection && !twoHasConnection)
            return -1;
        else if (!oneHasConnection && twoHasConnection)
            return 1;

        let oneAp = one.accessPoints[0] || null;
        let twoAp = two.accessPoints[0] || null;

        if (oneAp != null && twoAp == null)
            return -1;
        else if (oneAp == null && twoAp != null)
            return 1;

        let oneStrength = oneAp.strength;
        let twoStrength = twoAp.strength;

        // place stronger connections first
        if (oneStrength != twoStrength)
            return oneStrength < twoStrength ? 1 : -1;

        let oneHasSecurity = one.security != NMAccessPointSecurity.NONE;
        let twoHasSecurity = two.security != NMAccessPointSecurity.NONE;

        // place secure connections first
        // (we treat WEP/WPA/WPA2 the same as there is no way to
        // take them apart from the UI)
        if (oneHasSecurity && !twoHasSecurity)
            return -1;
        else if (!oneHasSecurity && twoHasSecurity)
            return 1;

        // sort alphabetically
        return GLib.utf8_collate(one.ssidText, two.ssidText);
    }

    _networkCompare(network, accessPoint) {
        if (!network.ssid.equal (accessPoint.get_ssid()))
            return false;
        if (network.mode != accessPoint.mode)
            return false;
        if (network.security != this._getApSecurityType(accessPoint))
            return false;

        return true;
    }

    _findExistingNetwork(accessPoint) {
        for (let i = 0; i < this._networks.length; i++) {
            let network = this._networks[i];
            for (let j = 0; j < network.accessPoints.length; j++) {
                if (network.accessPoints[j] == accessPoint)
                    return { network: i, ap: j };
            }
        }

        return null;
    }

    _findNetwork(accessPoint) {
        if (accessPoint.get_ssid() == null)
            return -1;

        for (let i = 0; i < this._networks.length; i++) {
            if (this._networkCompare(this._networks[i], accessPoint))
                return i;
        }
        return -1;
    }

    _checkConnections(network, accessPoint) {
        this._connections.forEach(connection => {
            if (accessPoint.connection_valid(connection) &&
                network.connections.indexOf(connection) == -1) {
                network.connections.push(connection);
            }
        });
    }

    _accessPointAdded(device, accessPoint) {
        if (accessPoint.get_ssid() == null) {
            // This access point is not visible yet
            // Wait for it to get a ssid
            accessPoint._notifySsidId = accessPoint.connect('notify::ssid', this._notifySsidCb.bind(this));
            return;
        }

        let pos = this._findNetwork(accessPoint);
        let network;

        if (pos != -1) {
            network = this._networks[pos];
            if (network.accessPoints.indexOf(accessPoint) != -1) {
                log('Access point was already seen, not adding again');
                return;
            }

            Util.insertSorted(network.accessPoints, accessPoint, (one, two) => {
                return two.strength - one.strength;
            });
            network.item.updateBestAP(network.accessPoints[0]);
            this._checkConnections(network, accessPoint);

            this._resortItems();
        } else {
            network = { ssid: accessPoint.get_ssid(),
                        mode: accessPoint.mode,
                        security: this._getApSecurityType(accessPoint),
                        connections: [ ],
                        item: null,
                        accessPoints: [ accessPoint ]
                      };
            network.ssidText = ssidToLabel(network.ssid);
            this._checkConnections(network, accessPoint);

            let newPos = Util.insertSorted(this._networks, network, this._networkSortFunction);
            this._createNetworkItem(network);
            this._itemBox.insert_child_at_index(network.item.actor, newPos);
        }

        this._queueSyncItemVisibility();
        this._syncView();
    }

    _queueSyncItemVisibility() {
        if (this._syncVisibilityId)
            return;

        this._syncVisibilityId = Meta.later_add(
            Meta.LaterType.BEFORE_REDRAW,
            () => {
                const { hasWindows } = Main.sessionMode;
                const { WPA2_ENT, WPA_ENT } = NMAccessPointSecurity;

                for (const network of this._networks) {
                    const [firstAp] = network.accessPoints;
                    network.item.visible =
                        hasWindows ||
                        network.connections.length > 0 ||
                        (firstAp._secType !== WPA2_ENT && firstAp._secType !== WPA_ENT);
                }
                return GLib.SOURCE_REMOVE;
            });
    }

    _accessPointRemoved(device, accessPoint) {
        let res = this._findExistingNetwork(accessPoint);

        if (res == null) {
            log('Removing an access point that was never added');
            return;
        }

        let network = this._networks[res.network];
        network.accessPoints.splice(res.ap, 1);

        if (network.accessPoints.length == 0) {
            network.item.actor.destroy();
            this._networks.splice(res.network, 1);
        } else {
            network.item.updateBestAP(network.accessPoints[0]);
            this._resortItems();
        }

        this._syncView();
    }

    _resortItems() {
        let adjustment = this._scrollView.vscroll.adjustment;
        let scrollValue = adjustment.value;

        this._itemBox.remove_all_children();
        this._networks.forEach(network => {
            this._itemBox.add_child(network.item.actor);
        });

        adjustment.value = scrollValue;
    }

    _selectNetwork(network) {
        if (this._selectedNetwork)
            this._selectedNetwork.item.actor.remove_style_pseudo_class('selected');

        this._selectedNetwork = network;
        this._updateSensitivity();

        if (this._selectedNetwork)
            this._selectedNetwork.item.actor.add_style_pseudo_class('selected');
    }

    _createNetworkItem(network) {
        network.item = new NMWirelessDialogItem(network);
        network.item.setActive(network == this._selectedNetwork);
        network.item.connect('selected', () => {
            Util.ensureActorVisibleInScrollView(this._scrollView, network.item.actor);
            this._selectNetwork(network);
        });
        network.item.actor.connect('destroy', () => {
            let keyFocus = global.stage.key_focus;
            if (keyFocus && keyFocus.contains(network.item.actor))
                this._itemBox.grab_key_focus();
        });
    }
};

var NMDeviceWireless = class {
    constructor(client, device) {
        this._client = client;
        this._device = device;

        this._description = '';

        this.item = new PopupMenu.PopupSubMenuMenuItem('', true);
        this.item.menu.addAction(_("Select Network"), this._showDialog.bind(this));

        this._toggleItem = new PopupMenu.PopupMenuItem('');
        this._toggleItem.connect('activate', this._toggleWifi.bind(this));
        this.item.menu.addMenuItem(this._toggleItem);

        this.item.menu.addSettingsAction(_("Wi-Fi Settings"), 'gnome-wifi-panel.desktop');

        this._wirelessEnabledChangedId = this._client.connect('notify::wireless-enabled', this._sync.bind(this));
        this._wirelessHwEnabledChangedId = this._client.connect('notify::wireless-hardware-enabled', this._sync.bind(this));
        this._activeApChangedId = this._device.connect('notify::active-access-point', this._activeApChanged.bind(this));
        this._stateChangedId = this._device.connect('state-changed', this._deviceStateChanged.bind(this));
        this._notifyConnectivityId = this._client.connect('notify::connectivity', this._iconChanged.bind(this));

        this._sync();
    }

    get category() {
        return NMConnectionCategory.WIRELESS;
    }

    _iconChanged() {
        this._sync();
        this.emit('icon-changed');
    }

    destroy() {
        if (this._activeApChangedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._activeApChangedId);
            this._activeApChangedId = 0;
        }
        if (this._stateChangedId) {
            GObject.Object.prototype.disconnect.call(this._device, this._stateChangedId);
            this._stateChangedId = 0;
        }
        if (this._strengthChangedId > 0) {
            this._activeAccessPoint.disconnect(this._strengthChangedId);
            this._strengthChangedId = 0;
        }
        if (this._wirelessEnabledChangedId) {
            this._client.disconnect(this._wirelessEnabledChangedId);
            this._wirelessEnabledChangedId = 0;
        }
        if (this._wirelessHwEnabledChangedId) {
            this._client.disconnect(this._wirelessHwEnabledChangedId);
            this._wirelessHwEnabledChangedId = 0;
        }
        if (this._dialog) {
            this._dialog.destroy();
            this._dialog = null;
        }
        if (this._notifyConnectivityId) {
            this._client.disconnect(this._notifyConnectivityId);
            this._notifyConnectivityId = 0;
        }

        this.item.destroy();
    }

    _deviceStateChanged(device, newstate, oldstate, reason) {
        if (newstate == oldstate) {
            log('device emitted state-changed without actually changing state');
            return;
        }

        /* Emit a notification if activation fails, but don't do it
           if the reason is no secrets, as that indicates the user
           cancelled the agent dialog */
        if (newstate == NM.DeviceState.FAILED &&
            reason != NM.DeviceStateReason.NO_SECRETS) {
            this.emit('activation-failed', reason);
        }

        this._sync();
    }

    _toggleWifi() {
        this._client.wireless_enabled = !this._client.wireless_enabled;
    }

    _showDialog() {
        this._dialog = new NMWirelessDialog(this._client, this._device);
        this._dialog.connect('closed', this._dialogClosed.bind(this));
        this._dialog.open();
    }

    _dialogClosed() {
        this._dialog = null;
    }

    _strengthChanged() {
        this._iconChanged();
    }

    _activeApChanged() {
        if (this._activeAccessPoint) {
            this._activeAccessPoint.disconnect(this._strengthChangedId);
            this._strengthChangedId = 0;
        }

        this._activeAccessPoint = this._device.active_access_point;

        if (this._activeAccessPoint) {
            this._strengthChangedId = this._activeAccessPoint.connect('notify::strength',
                                                                      this._strengthChanged.bind(this));
        }

        this._sync();
    }

    _sync() {
        this._toggleItem.label.text = this._client.wireless_enabled ? _("Turn Off") : _("Turn On");
        this._toggleItem.actor.visible = this._client.wireless_hardware_enabled;

        this.item.icon.icon_name = this._getMenuIcon();
        this.item.label.text = this._getStatus();
    }

    setDeviceDescription(desc) {
        this._description = desc;
        this._sync();
    }

    _getStatus() {
        let ap = this._device.active_access_point;

        if (this._isHotSpotMaster()) {
            /* Translators: %s is a network identifier */
            return _("%s Hotspot Active").format(this._description);
        } else if (this._device.state >= NM.DeviceState.PREPARE &&
                 this._device.state < NM.DeviceState.ACTIVATED) {
            /* Translators: %s is a network identifier */
            return _("%s Connecting").format(this._description);
        } else if (ap) {
            const ssid = ap.get_ssid();
            if (ssid)
                return ssidToLabel(ssid);

            // Use connection name when connected to hidden AP
            const activeConnection = this._device.get_active_connection();
            if (activeConnection)
                return activeConnection.connection.get_id();

            return ssidToLabel(null);
        } else if (!this._client.wireless_hardware_enabled) {
            /* Translators: %s is a network identifier */
            return _("%s Hardware Disabled").format(this._description);
        } else if (!this._client.wireless_enabled) {
            /* Translators: %s is a network identifier */
            return _("%s Off").format(this._description);
        } else if (this._device.state == NM.DeviceState.DISCONNECTED) {
            /* Translators: %s is a network identifier */
            return _("%s Not Connected").format(this._description);
        } else {
            return '';
        }
    }

    _getMenuIcon() {
        if (this._device.active_connection)
            return this.getIndicatorIcon();
        else
            return 'network-wireless-signal-none-symbolic';
    }

    _canReachInternet() {
        if (this._client.primary_connection != this._device.active_connection)
            return true;

        return this._client.connectivity == NM.ConnectivityState.FULL;
    }

    _isHotSpotMaster() {
        if (!this._device.active_connection)
            return false;

        let connection = this._device.active_connection.connection;
        if (!connection)
            return false;

        let ip4config = connection.get_setting_ip4_config();
        if (!ip4config)
            return false;

        return ip4config.get_method() == NM.SETTING_IP4_CONFIG_METHOD_SHARED;
    }

    getIndicatorIcon() {
        if (this._device.state < NM.DeviceState.PREPARE)
            return 'network-wireless-disconnected-symbolic';
        if (this._device.state < NM.DeviceState.ACTIVATED)
            return 'network-wireless-acquiring-symbolic';

        if (this._isHotSpotMaster())
            return 'network-wireless-hotspot-symbolic';

        let ap = this._device.active_access_point;
        if (!ap) {
            if (this._device.mode != NM80211Mode.ADHOC)
                log('An active wireless connection, in infrastructure mode, involves no access point?');

            if (this._canReachInternet())
                return 'network-wireless-connected-symbolic';
            else
                return 'network-wireless-no-route-symbolic';
        }

        if (this._canReachInternet())
            return 'network-wireless-signal-' + signalToIcon(ap.strength) + '-symbolic';
        else
            return 'network-wireless-no-route-symbolic';
    }
};
Signals.addSignalMethods(NMDeviceWireless.prototype);

var NMVpnConnectionItem = class extends NMConnectionItem {
    isActive() {
        if (this._activeConnection == null)
            return false;

        return this._activeConnection.vpn_state != NM.VpnConnectionState.DISCONNECTED;
    }

    _buildUI() {
        this.labelItem = new PopupMenu.PopupMenuItem('');
        this.labelItem.connect('activate', this._toggle.bind(this));

        this.radioItem = new PopupMenu.PopupSwitchMenuItem(this._connection.get_id(), false);
        this.radioItem.connect('toggled', this._toggle.bind(this));
    }

    _sync() {
        let isActive = this.isActive();
        this.labelItem.label.text = isActive ? _("Turn Off") : this._section.getConnectLabel();
        this.radioItem.setToggleState(isActive);
        this.radioItem.setStatus(this._getStatus());
        this.emit('icon-changed');
    }

    _getStatus() {
        if (this._activeConnection == null)
            return null;

        switch(this._activeConnection.vpn_state) {
        case NM.VpnConnectionState.DISCONNECTED:
        case NM.VpnConnectionState.ACTIVATED:
            return null;
        case NM.VpnConnectionState.PREPARE:
        case NM.VpnConnectionState.CONNECT:
        case NM.VpnConnectionState.IP_CONFIG_GET:
            return _("connecting…");
        case NM.VpnConnectionState.NEED_AUTH:
            /* Translators: this is for network connections that require some kind of key or password */
            return _("authentication required");
        case NM.VpnConnectionState.FAILED:
            return _("connection failed");
        default:
            return 'invalid';
        }
    }

    _connectionStateChanged(ac, newstate, reason) {
        if (newstate == NM.VpnConnectionState.FAILED &&
            reason != NM.VpnConnectionStateReason.NO_SECRETS) {
            // FIXME: if we ever want to show something based on reason,
            // we need to convert from NM.VpnConnectionStateReason
            // to NM.DeviceStateReason
            this.emit('activation-failed', reason);
        }

        this.emit('icon-changed');
        super._connectionStateChanged();
    }

    setActiveConnection(activeConnection) {
        if (this._activeConnectionChangedId > 0) {
            this._activeConnection.disconnect(this._activeConnectionChangedId);
            this._activeConnectionChangedId = 0;
        }

        this._activeConnection = activeConnection;

        if (this._activeConnection)
            this._activeConnectionChangedId = this._activeConnection.connect('vpn-state-changed',
                                                                             this._connectionStateChanged.bind(this));

        this._sync();
    }

    getIndicatorIcon() {
        if (this._activeConnection) {
            if (this._activeConnection.vpn_state < NM.VpnConnectionState.ACTIVATED)
                return 'network-vpn-acquiring-symbolic';
            else
                return 'network-vpn-symbolic';
        } else {
            return '';
        }
    }
};

var NMVpnSection = class extends NMConnectionSection {
    constructor(client) {
        super(client);

        this.item.menu.addSettingsAction(_("VPN Settings"), 'gnome-network-panel.desktop');

        this._sync();
    }

    _sync() {
        let nItems = this._connectionItems.size;
        this.item.actor.visible = (nItems > 0);

        super._sync();
    }

    get category() {
        return NMConnectionCategory.VPN;
    }

    _getDescription() {
        return _("VPN");
    }

    _getStatus() {
        let values = this._connectionItems.values();
        for (let item of values) {
            if (item.isActive())
                return item.getName();
        }

        return _("VPN Off");
    }

    _getMenuIcon() {
        return this.getIndicatorIcon() || 'network-vpn-symbolic';
    }

    activateConnection(connection) {
        this._client.activate_connection_async(connection, null, null, null, null);
    }

    deactivateConnection(activeConnection) {
        this._client.deactivate_connection(activeConnection, null);
    }

    setActiveConnections(vpnConnections) {
        let connections = this._connectionItems.values();
        for (let item of connections) {
            item.setActiveConnection(null);
        }
        vpnConnections.forEach(a => {
            if (a.connection) {
                let item = this._connectionItems.get(a.connection.get_uuid());
                item.setActiveConnection(a);
            }
        });
    }

    _makeConnectionItem(connection) {
        return new NMVpnConnectionItem(this, connection);
    }

    getIndicatorIcon() {
        let items = this._connectionItems.values();
        for (let item of items) {
            let icon = item.getIndicatorIcon();
            if (icon)
                return icon;
        }
        return '';
    }
};
Signals.addSignalMethods(NMVpnSection.prototype);

var DeviceCategory = class extends PopupMenu.PopupMenuSection {
    constructor(category) {
        super();

        this._category = category;

        this.devices = [];

        this.section = new PopupMenu.PopupMenuSection();
        this.section.box.connect('actor-added', this._sync.bind(this));
        this.section.box.connect('actor-removed', this._sync.bind(this));
        this.addMenuItem(this.section);

        this._summaryItem = new PopupMenu.PopupSubMenuMenuItem('', true);
        this._summaryItem.icon.icon_name = this._getSummaryIcon();
        this.addMenuItem(this._summaryItem);

        this._summaryItem.menu.addSettingsAction(_('Network Settings'),
                                                 'gnome-network-panel.desktop');
        this._summaryItem.actor.hide();

    }

    _sync() {
        let nDevices = this.section.box.get_children().reduce(
            (prev, child) => prev + (child.visible ? 1 : 0), 0);
        this._summaryItem.label.text = this._getSummaryLabel(nDevices);
        let shouldSummarize = nDevices > MAX_DEVICE_ITEMS;
        this._summaryItem.actor.visible = shouldSummarize;
        this.section.actor.visible = !shouldSummarize;
    }

    _getSummaryIcon() {
        switch(this._category) {
            case NMConnectionCategory.WIRED:
                return 'network-wired-symbolic';
            case NMConnectionCategory.WIRELESS:
            case NMConnectionCategory.WWAN:
                return 'network-wireless-symbolic';
        }
        return '';
    }

    _getSummaryLabel(nDevices) {
        switch(this._category) {
            case NMConnectionCategory.WIRED:
                return ngettext("%s Wired Connection",
                                "%s Wired Connections",
                                nDevices).format(nDevices);
            case NMConnectionCategory.WIRELESS:
                return ngettext("%s Wi-Fi Connection",
                                "%s Wi-Fi Connections",
                                nDevices).format(nDevices);
            case NMConnectionCategory.WWAN:
                return ngettext("%s Modem Connection",
                                "%s Modem Connections",
                                nDevices).format(nDevices);
        }
        return '';
    }
};

class CaptivePortalHandler {
    constructor(checkUri) {
        this._checkUri = checkUri;
        this._connectivityQueue = new Set();
        this._notifications = new Map();
        this._portalHelperProxy = null;
    }

    addConnection(name, path) {
        if (this._connectivityQueue.has(path) || this._notifications.has(path))
            return;

        const source = new MessageTray.Source(
            _('System'),
            'emblem-system-symbolic');
        Main.messageTray.add(source);

        const notification = new MessageTray.Notification(
            source, _('Sign Into Wi–Fi Network'), name);
        notification.connect('activated',
            () => this._onNotificationActivated(path));
        notification.connect('destroy',
            () => this._notifications.delete(path));
        this._notifications.set(path, notification);
        source.notify(notification);
    }


    removeConnection(path) {
        if (this._connectivityQueue.delete(path) && this._portalHelperProxy)
            this._portalHelperProxy.CloseRemote(path);
        const notification = this._notifications.get(path);
        if (notification)
            notification.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
        this._notifications.delete(path);
    }

    _onNotificationActivated(path) {
        const context = global.create_app_launch_context(
            global.get_current_time(), -1);

        if (Config.HAVE_PORTAL_HELPER)
            this._launchPortalHelper(path, context);
        else
            Gio.AppInfo.launch_default_for_uri(this._checkUri, context);

        Main.overview.hide();
        Main.panel.closeCalendar();
    }

    _portalHelperDone(parameters) {
        const [path, result] = parameters;

        if (result === PortalHelperResult.CANCELLED) {
            // Keep the connection in the queue, so the user is not
            // spammed with more logins until we next flush the queue,
            // which will happen once they choose a better connection
            // or we get to full connectivity through other means
        } else if (result === PortalHelperResult.COMPLETED) {
            this.removeConnection(path);
        } else if (result === PortalHelperResult.RECHECK) {
            this.emit('recheck', path);
        } else {
            log(`Invalid result from portal helper: ${result}`);
        }
    }

    _launchPortalHelper(path, context) {
        const {timestamp} = context;
        if (this._portalHelperProxy) {
            this._portalHelperProxy.AuthenticateRemote(path, this._checkUri, timestamp);
        } else {
            new PortalHelperProxy(Gio.DBus.session,
                'org.gnome.Shell.PortalHelper',
                '/org/gnome/Shell/PortalHelper',
                (proxy, error) => {
                    if (error) {
                        log(`Error launching the portal helper: ${e.message}`);
                        return;
                    }

                    this._portalHelperProxy = proxy;
                    this._portalHelperProxy.connectSignal('Done',
                        (proxy, emitter, params) => {
                            this._portalHelperDone(params);
                        });
                    this._portalHelperProxy.AuthenticateRemote(path, this._checkUri, timestamp);
                });
        }

        this._connectivityQueue.add(path);
    }

    clear() {
        if (this._portalHelperProxy) {
            for (const item of this._connectivityQueue)
                this._portalHelperProxy.CloseRemote(item);
        }
        this._connectivityQueue.clear();

        for (const n of this._notifications.values())
            n.destroy(MessageTray.NotificationDestroyedReason.SOURCE_CLOSED);
        this._notifications.clear();
    }
}
Signals.addSignalMethods(CaptivePortalHandler.prototype);

var NMApplet = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._primaryIndicator = this._addIndicator();
        this._vpnIndicator = this._addIndicator();

        // Device types
        this._dtypes = { };
        this._dtypes[NM.DeviceType.ETHERNET] = NMDeviceWired;
        this._dtypes[NM.DeviceType.WIFI] = NMDeviceWireless;
        this._dtypes[NM.DeviceType.MODEM] = NMDeviceModem;
        this._dtypes[NM.DeviceType.BT] = NMDeviceBluetooth;

        // Connection types
        this._ctypes = { };
        this._ctypes[NM.SETTING_WIRED_SETTING_NAME] = NMConnectionCategory.WIRED;
        this._ctypes[NM.SETTING_WIRELESS_SETTING_NAME] = NMConnectionCategory.WIRELESS;
        this._ctypes[NM.SETTING_BLUETOOTH_SETTING_NAME] = NMConnectionCategory.WWAN;
        this._ctypes[NM.SETTING_CDMA_SETTING_NAME] = NMConnectionCategory.WWAN;
        this._ctypes[NM.SETTING_GSM_SETTING_NAME] = NMConnectionCategory.WWAN;
        this._ctypes[NM.SETTING_VPN_SETTING_NAME] = NMConnectionCategory.VPN;

        NM.Client.new_async(null, this._clientGot.bind(this));
    }

    _clientGot(obj, result) {
        this._client = NM.Client.new_finish(result);

        this._activeConnections = [ ];
        this._connections = [ ];
        this._connectivityQueue = [ ];

        this._mainConnection = null;
        this._mainConnectionIconChangedId = 0;
        this._mainConnectionStateChangedId = 0;

        this._notification = null;

        this._nmDevices = [];
        this._devices = { };

        let categories = [NMConnectionCategory.WIRED,
                          NMConnectionCategory.WIRELESS,
                          NMConnectionCategory.WWAN];
        for (let category of categories) {
            this._devices[category] = new DeviceCategory(category);
            this.menu.addMenuItem(this._devices[category]);
        }

        this._vpnSection = new NMVpnSection(this._client);
        this._vpnSection.connect('activation-failed', this._onActivationFailed.bind(this));
        this._vpnSection.connect('icon-changed', this._updateIcon.bind(this));
        this.menu.addMenuItem(this._vpnSection.item);

        const {connectivityCheckUri} = this._client;
        this._portalHandler = new CaptivePortalHandler(connectivityCheckUri);
        this._portalHandler.connect('recheck', async (o, path) => {
            try {
                const state = await this._client.check_connectivity_async(null);
                if (state >= NM.ConnectivityState.FULL)
                    this._portalHandler.removeConnection(path);
            } catch (e) { }
        });

        this._readConnections();
        this._readDevices();
        this._syncNMState();
        this._syncMainConnection();
        this._syncVpnConnections();

        this._client.connect('notify::nm-running', this._syncNMState.bind(this));
        this._client.connect('notify::networking-enabled', this._syncNMState.bind(this));
        this._client.connect('notify::state', this._syncNMState.bind(this));
        this._client.connect('notify::primary-connection', this._syncMainConnection.bind(this));
        this._client.connect('notify::activating-connection', this._syncMainConnection.bind(this));
        this._client.connect('notify::active-connections', this._syncVpnConnections.bind(this));
        this._client.connect('notify::connectivity', this._syncConnectivity.bind(this));
        this._client.connect('device-added', this._deviceAdded.bind(this));
        this._client.connect('device-removed', this._deviceRemoved.bind(this));
        this._client.connect('connection-added', this._connectionAdded.bind(this));
        this._client.connect('connection-removed', this._connectionRemoved.bind(this));

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));

        this._configPermission = null;
        Polkit.Permission.new(
            'org.freedesktop.NetworkManager.network-control', null, null,
            (o, res) => {
                try {
                    this._configPermission = Polkit.Permission.new_finish(res);
                } catch (e) {
                    log('No permission to control network connections: %s'.format(e.toString()));
                }
                this._sessionUpdated();
            });
    }

    _sessionUpdated() {
        const sensitive =
            !Main.sessionMode.isLocked &&
            this._configPermission && this._configPermission.allowed;
        this.menu.setSensitive(sensitive);
    }

    _ensureSource() {
        if (!this._source) {
            this._source = new MessageTray.Source(_("Network Manager"),
                                                  'network-transmit-receive');
            this._source.policy = new MessageTray.NotificationApplicationPolicy('gnome-network-panel');

            this._source.connect('destroy', () => { this._source = null; });
            Main.messageTray.add(this._source);
        }
    }

    _readDevices() {
        let devices = this._client.get_devices() || [ ];
        for (let i = 0; i < devices.length; ++i) {
            try {
                this._deviceAdded(this._client, devices[i], true);
            } catch (e) {
                log(`Failed to add device ${devices[i]}: ${e}`);
            }
        }
        this._syncDeviceNames();
    }

    _notify(iconName, title, text, urgency) {
        if (this._notification)
            this._notification.destroy();

        this._ensureSource();

        let gicon = new Gio.ThemedIcon({ name: iconName });
        this._notification = new MessageTray.Notification(this._source, title, text, { gicon: gicon });
        this._notification.setUrgency(urgency);
        this._notification.setTransient(true);
        this._notification.connect('destroy', () => {
            this._notification = null;
        });
        this._source.notify(this._notification);
    }

    _onActivationFailed(device, reason) {
        // XXX: nm-applet has no special text depending on reason
        // but I'm not sure of this generic message
        this._notify('network-error-symbolic',
                     _("Connection failed"),
                     _("Activation of network connection failed"),
                     MessageTray.Urgency.HIGH);
    }

    _syncDeviceNames() {
        let names = NM.Device.disambiguate_names(this._nmDevices);
        for (let i = 0; i < this._nmDevices.length; i++) {
            let device = this._nmDevices[i];
            let description = names[i];
            if (device._delegate)
                device._delegate.setDeviceDescription(description);
        }
    }

    _deviceAdded(client, device, skipSyncDeviceNames) {
        if (device._delegate) {
            // already seen, not adding again
            return;
        }

        let wrapperClass = this._dtypes[device.get_device_type()];
        if (wrapperClass) {
            let wrapper = new wrapperClass(this._client, device);
            device._delegate = wrapper;
            this._addDeviceWrapper(wrapper);

            this._nmDevices.push(device);
            this._deviceChanged(device, skipSyncDeviceNames);

            device.connect('notify::interface', () => {
                this._deviceChanged(device, false);
            });
        }
    }

    _deviceChanged(device, skipSyncDeviceNames) {
        let wrapper = device._delegate;

        if (!skipSyncDeviceNames)
            this._syncDeviceNames();

        if (wrapper instanceof NMConnectionSection) {
            this._connections.forEach(connection => {
                wrapper.checkConnection(connection);
            });
        }
    }

    _addDeviceWrapper(wrapper) {
        wrapper._activationFailedId = wrapper.connect('activation-failed',
                                                      this._onActivationFailed.bind(this));

        let section = this._devices[wrapper.category].section;
        section.addMenuItem(wrapper.item);

        let devices = this._devices[wrapper.category].devices;
        devices.push(wrapper);
    }

    _deviceRemoved(client, device) {
        let pos = this._nmDevices.indexOf(device);
        if (pos != -1) {
            this._nmDevices.splice(pos, 1);
            this._syncDeviceNames();
        }

        let wrapper = device._delegate;
        if (!wrapper) {
            log('Removing a network device that was not added');
            return;
        }

        this._removeDeviceWrapper(wrapper);
    }

    _removeDeviceWrapper(wrapper) {
        wrapper.disconnect(wrapper._activationFailedId);
        wrapper.destroy();

        let devices = this._devices[wrapper.category].devices;
        let pos = devices.indexOf(wrapper);
        devices.splice(pos, 1);
    }

    _getMainConnection() {
        let connection;

        connection = this._client.get_primary_connection();
        if (connection) {
            ensureActiveConnectionProps(connection, this._client);
            return connection;
        }

        connection = this._client.get_activating_connection();
        if (connection) {
            ensureActiveConnectionProps(connection, this._client);
            return connection;
        }

        return null;
    }

    _syncMainConnection() {
        if (this._mainConnectionIconChangedId > 0) {
            this._mainConnection._primaryDevice.disconnect(this._mainConnectionIconChangedId);
            this._mainConnectionIconChangedId = 0;
        }

        if (this._mainConnectionStateChangedId > 0) {
            this._mainConnection.disconnect(this._mainConnectionStateChangedId);
            this._mainConnectionStateChangedId = 0;
        }

        this._mainConnection = this._getMainConnection();

        if (this._mainConnection) {
            if (this._mainConnection._primaryDevice)
                this._mainConnectionIconChangedId = this._mainConnection._primaryDevice.connect('icon-changed', this._updateIcon.bind(this));
            this._mainConnectionStateChangedId = this._mainConnection.connect('notify::state', this._mainConnectionStateChanged.bind(this));
            this._mainConnectionStateChanged();
        }

        this._updateIcon();
        this._syncConnectivity();
    }

    _syncVpnConnections() {
        let activeConnections = this._client.get_active_connections() || [];
        let vpnConnections = activeConnections.filter(
            a => (a instanceof NM.VpnConnection)
        );
        vpnConnections.forEach(a => {
            ensureActiveConnectionProps(a, this._client);
        });
        this._vpnSection.setActiveConnections(vpnConnections);

        this._updateIcon();
    }

    _mainConnectionStateChanged() {
        if (this._mainConnection.state == NM.ActiveConnectionState.ACTIVATED && this._notification)
            this._notification.destroy();
    }

    _ignoreConnection(connection) {
        let setting = connection.get_setting_connection();
        if (!setting)
            return true;

        // Ignore slave connections
        if (setting.get_master())
            return true;

        return false;
    }

    _addConnection(connection) {
        if (this._ignoreConnection(connection))
            return;
        if (connection._updatedId) {
            // connection was already seen
            return;
        }

        connection._updatedId = connection.connect('changed', this._updateConnection.bind(this));

        this._updateConnection(connection);
        this._connections.push(connection);
    }

    _readConnections() {
        let connections = this._client.get_connections();
        connections.forEach(this._addConnection.bind(this));
    }

    _connectionAdded(client, connection) {
        this._addConnection(connection);
    }

    _connectionRemoved(client, connection) {
        let pos = this._connections.indexOf(connection);
        if (pos != -1)
            this._connections.splice(pos, 1);

        let section = connection._section;

        if (section == NMConnectionCategory.INVALID)
            return;

        if (section == NMConnectionCategory.VPN) {
            this._vpnSection.removeConnection(connection);
        } else {
            let devices = this._devices[section].devices;
            for (let i = 0; i < devices.length; i++) {
                if (devices[i] instanceof NMConnectionSection)
                    devices[i].removeConnection(connection);
            }
        }

        connection.disconnect(connection._updatedId);
        connection._updatedId = 0;
    }

    _updateConnection(connection) {
        let connectionSettings = connection.get_setting_by_name(NM.SETTING_CONNECTION_SETTING_NAME);
        connection._type = connectionSettings.type;
        connection._section = this._ctypes[connection._type] || NMConnectionCategory.INVALID;

        let section = connection._section;

        if (section == NMConnectionCategory.INVALID)
            return;

        if (section == NMConnectionCategory.VPN) {
            this._vpnSection.checkConnection(connection);
        } else {
            let devices = this._devices[section].devices;
            devices.forEach(wrapper => {
                if (wrapper instanceof NMConnectionSection)
                    wrapper.checkConnection(connection);
            });
        }
    }

    _syncNMState() {
        this.indicators.visible = this._client.nm_running;
        this.menu.actor.visible = this._client.networking_enabled;

        this._updateIcon();
        this._syncConnectivity();
    }

    _syncConnectivity() {
        if (this._mainConnection == null ||
            this._mainConnection.state != NM.ActiveConnectionState.ACTIVATED) {
            this._portalHandler.clear();
            return;
        }

        let isPortal = this._client.connectivity == NM.ConnectivityState.PORTAL;
        // For testing, allow interpreting any value != FULL as PORTAL, because
        // LIMITED (no upstream route after the default gateway) is easy to obtain
        // with a tethered phone
        // NONE is also possible, with a connection configured to force no default route
        // (but in general we should only prompt a portal if we know there is a portal)
        if (GLib.getenv('GNOME_SHELL_CONNECTIVITY_TEST') != null)
            isPortal = isPortal || this._client.connectivity < NM.ConnectivityState.FULL;
        if (!isPortal || Main.sessionMode.isGreeter)
            return;

        this._portalHandler.addConnection(
            this._mainConnection.get_id(),
            this._mainConnection.get_path());
    }

    _updateIcon() {
        if (!this._client.networking_enabled) {
            this._primaryIndicator.visible = false;
        } else {
            let dev = null;
            if (this._mainConnection)
                dev = this._mainConnection._primaryDevice;

            let state = this._client.get_state();
            let connected = state == NM.State.CONNECTED_GLOBAL;
            this._primaryIndicator.visible = (dev != null) || connected;
            if (dev) {
                this._primaryIndicator.icon_name = dev.getIndicatorIcon();
            } else if (connected) {
                if (this._client.connectivity == NM.ConnectivityState.FULL)
                    this._primaryIndicator.icon_name = 'network-wired-symbolic';
                else
                    this._primaryIndicator.icon_name = 'network-wired-no-route-symbolic';
            }
        }

        this._vpnIndicator.icon_name = this._vpnSection.getIndicatorIcon();
        this._vpnIndicator.visible = (this._vpnIndicator.icon_name != '');
    }
};
(uuay)windowManager.js�_// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const AltTab = imports.ui.altTab;
const AppFavorites = imports.ui.appFavorites;
const Dialog = imports.ui.dialog;
const WorkspaceSwitcherPopup = imports.ui.workspaceSwitcherPopup;
const InhibitShortcutsDialog = imports.ui.inhibitShortcutsDialog;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;
const Tweener = imports.ui.tweener;
const WindowMenu = imports.ui.windowMenu;
const PadOsd = imports.ui.padOsd;
const EdgeDragAction = imports.ui.edgeDragAction;
const CloseDialog = imports.ui.closeDialog;
const SwitchMonitor = imports.ui.switchMonitor;

const { loadInterfaceXML } = imports.misc.fileUtils;

var SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
var MINIMIZE_WINDOW_ANIMATION_TIME = 0.2;
var SHOW_WINDOW_ANIMATION_TIME = 0.15;
var DIALOG_SHOW_WINDOW_ANIMATION_TIME = 0.1;
var DESTROY_WINDOW_ANIMATION_TIME = 0.15;
var DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 0.1;
var WINDOW_ANIMATION_TIME = 0.25;
var DIM_BRIGHTNESS = -0.3;
var DIM_TIME = 0.500;
var UNDIM_TIME = 0.250;
var MOTION_THRESHOLD = 100;

var ONE_SECOND = 1000; // in ms

const GSD_WACOM_BUS_NAME = 'org.gnome.SettingsDaemon.Wacom';
const GSD_WACOM_OBJECT_PATH = '/org/gnome/SettingsDaemon/Wacom';

const GsdWacomIface = loadInterfaceXML('org.gnome.SettingsDaemon.Wacom');
const GsdWacomProxy = Gio.DBusProxy.makeProxyWrapper(GsdWacomIface);

var DisplayChangeDialog = class extends ModalDialog.ModalDialog {
    constructor(wm) {
        super({ styleClass: 'prompt-dialog' });

        this._wm = wm;

        this._countDown = Meta.MonitorManager.get_display_configuration_timeout();

        let iconName = 'preferences-desktop-display-symbolic';
        let icon = new Gio.ThemedIcon({ name: iconName });
        let title = _("Do you want to keep these display settings?");
        let body = this._formatCountDown();

        this._content = new Dialog.MessageDialogContent({ icon, title, body });

        this.contentLayout.add(this._content,
                               { x_fill: true,
                                 y_fill: true });

        /* Translators: this and the following message should be limited in lenght,
           to avoid ellipsizing the labels.
        */
        this._cancelButton = this.addButton({ label: _("Revert Settings"),
                                              action: this._onFailure.bind(this),
                                              key: Clutter.Escape });
        this._okButton = this.addButton({ label:  _("Keep Changes"),
                                          action: this._onSuccess.bind(this),
                                          default: true });

        this._timeoutId = Mainloop.timeout_add(ONE_SECOND, this._tick.bind(this));
        GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._tick');
    }

    close(timestamp) {
        if (this._timeoutId > 0) {
            Mainloop.source_remove(this._timeoutId);
            this._timeoutId = 0;
        }

        super.close(timestamp);
    }

    _formatCountDown() {
        let fmt = ngettext("Settings changes will revert in %d second",
                           "Settings changes will revert in %d seconds");
        return fmt.format(this._countDown);
    }

    _tick() {
        this._countDown--;

        if (this._countDown == 0) {
            /* mutter already takes care of failing at timeout */
            this._timeoutId = 0;
            this.close();
            return GLib.SOURCE_REMOVE;
        }

        this._content.body = this._formatCountDown();
        return GLib.SOURCE_CONTINUE;
    }

    _onFailure() {
        this._wm.complete_display_change(false);
        this.close();
    }

    _onSuccess() {
        this._wm.complete_display_change(true);
        this.close();
    }
};

var WindowDimmer = class {
    constructor(actor) {
        this._brightnessEffect = new Clutter.BrightnessContrastEffect();
        actor.add_effect(this._brightnessEffect);
        this.actor = actor;
        this._enabled = true;
        this._dimFactor = 0.0;
        this._syncEnabled();
    }

    _syncEnabled() {
        this._brightnessEffect.enabled = (this._enabled && this._dimFactor > 0);
    }

    setEnabled(enabled) {
        this._enabled = enabled;
        this._syncEnabled();
    }

    set dimFactor(factor) {
        this._dimFactor = factor;
        this._brightnessEffect.set_brightness(factor * DIM_BRIGHTNESS);
        this._syncEnabled();
    }

    get dimFactor() {
        return this._dimFactor;
    }
};

function getWindowDimmer(actor) {
    let enabled = Meta.prefs_get_attach_modal_dialogs();
    if (actor._windowDimmer)
        actor._windowDimmer.setEnabled(enabled);

    if (enabled) {
        if (!actor._windowDimmer)
            actor._windowDimmer = new WindowDimmer(actor);
        return actor._windowDimmer;
    } else {
        return null;
    }
}

/*
 * When the last window closed on a workspace is a dialog or splash
 * screen, we assume that it might be an initial window shown before
 * the main window of an application, and give the app a grace period
 * where it can map another window before we remove the workspace.
 */
var LAST_WINDOW_GRACE_TIME = 1000;

var WorkspaceTracker = class {
    constructor(wm) {
        this._wm = wm;

        this._workspaces = [];
        this._checkWorkspacesId = 0;

        this._pauseWorkspaceCheck = false;

        let tracker = Shell.WindowTracker.get_default();
        tracker.connect('startup-sequence-changed', this._queueCheckWorkspaces.bind(this));

        let workspaceManager = global.workspace_manager;
        workspaceManager.connect('notify::n-workspaces',
                                 this._nWorkspacesChanged.bind(this));
        global.window_manager.connect('switch-workspace',
                                      this._queueCheckWorkspaces.bind(this));

        global.display.connect('window-entered-monitor',
                               this._windowEnteredMonitor.bind(this));
        global.display.connect('window-left-monitor',
                               this._windowLeftMonitor.bind(this));
        global.display.connect('restacked',
                               this._windowsRestacked.bind(this));

        this._workspaceSettings = new Gio.Settings({ schema_id: 'org.gnome.mutter' });
        this._workspaceSettings.connect('changed::dynamic-workspaces', this._queueCheckWorkspaces.bind(this));

        this._nWorkspacesChanged();
    }

    blockUpdates() {
        this._pauseWorkspaceCheck = true;
    }

    unblockUpdates() {
        this._pauseWorkspaceCheck = false;
    }

    _checkWorkspaces() {
        let workspaceManager = global.workspace_manager;
        let i;
        let emptyWorkspaces = [];

        if (!Meta.prefs_get_dynamic_workspaces()) {
            this._checkWorkspacesId = 0;
            return false;
        }

        // Update workspaces only if Dynamic Workspace Management has not been paused by some other function
        if (this._pauseWorkspaceCheck)
            return true;

        for (i = 0; i < this._workspaces.length; i++) {
            let lastRemoved = this._workspaces[i]._lastRemovedWindow;
            if ((lastRemoved &&
                 (lastRemoved.get_window_type() == Meta.WindowType.SPLASHSCREEN ||
                  lastRemoved.get_window_type() == Meta.WindowType.DIALOG ||
                  lastRemoved.get_window_type() == Meta.WindowType.MODAL_DIALOG)) ||
                this._workspaces[i]._keepAliveId)
                emptyWorkspaces[i] = false;
            else
                emptyWorkspaces[i] = true;
        }

        let sequences = Shell.WindowTracker.get_default().get_startup_sequences();
        for (i = 0; i < sequences.length; i++) {
            let index = sequences[i].get_workspace();
            if (index >= 0 && index <= workspaceManager.n_workspaces)
                emptyWorkspaces[index] = false;
        }

        let windows = global.get_window_actors();
        for (i = 0; i < windows.length; i++) {
            let actor = windows[i];
            let win = actor.get_meta_window();

            if (win.is_on_all_workspaces())
                continue;

            let workspaceIndex = win.get_workspace().index();
            emptyWorkspaces[workspaceIndex] = false;
        }

        // If we don't have an empty workspace at the end, add one
        if (!emptyWorkspaces[emptyWorkspaces.length -1]) {
            workspaceManager.append_new_workspace(false, global.get_current_time());
            emptyWorkspaces.push(true);
        }

        let lastIndex = emptyWorkspaces.length - 1;
        let lastEmptyIndex = emptyWorkspaces.lastIndexOf(false) + 1;
        let activeWorkspaceIndex = workspaceManager.get_active_workspace_index();
        emptyWorkspaces[activeWorkspaceIndex] = false;

        // Delete empty workspaces except for the last one; do it from the end
        // to avoid index changes
        for (i = lastIndex; i >= 0; i--) {
            if (emptyWorkspaces[i] && i != lastEmptyIndex)
                workspaceManager.remove_workspace(this._workspaces[i], global.get_current_time());
        }

        this._checkWorkspacesId = 0;
        return false;
    }

    keepWorkspaceAlive(workspace, duration) {
        if (workspace._keepAliveId)
            Mainloop.source_remove(workspace._keepAliveId);

        workspace._keepAliveId = Mainloop.timeout_add(duration, () => {
            workspace._keepAliveId = 0;
            this._queueCheckWorkspaces();
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(workspace._keepAliveId, '[gnome-shell] this._queueCheckWorkspaces');
    }

    _windowRemoved(workspace, window) {
        workspace._lastRemovedWindow = window;
        this._queueCheckWorkspaces();
        let id = Mainloop.timeout_add(LAST_WINDOW_GRACE_TIME, () => {
            if (workspace._lastRemovedWindow == window) {
                workspace._lastRemovedWindow = null;
                this._queueCheckWorkspaces();
            }
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(id, '[gnome-shell] this._queueCheckWorkspaces');
    }

    _windowLeftMonitor(metaDisplay, monitorIndex, metaWin) {
        // If the window left the primary monitor, that
        // might make that workspace empty
        if (monitorIndex == Main.layoutManager.primaryIndex)
            this._queueCheckWorkspaces();
    }

    _windowEnteredMonitor(metaDisplay, monitorIndex, metaWin) {
        // If the window entered the primary monitor, that
        // might make that workspace non-empty
        if (monitorIndex == Main.layoutManager.primaryIndex)
            this._queueCheckWorkspaces();
    }

    _windowsRestacked() {
        // Figure out where the pointer is in case we lost track of
        // it during a grab. (In particular, if a trayicon popup menu
        // is dismissed, see if we need to close the message tray.)
        global.sync_pointer();
    }

    _queueCheckWorkspaces() {
        if (this._checkWorkspacesId == 0)
            this._checkWorkspacesId = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, this._checkWorkspaces.bind(this));
    }

    _nWorkspacesChanged() {
        let workspaceManager = global.workspace_manager;
        let oldNumWorkspaces = this._workspaces.length;
        let newNumWorkspaces = workspaceManager.n_workspaces;

        if (oldNumWorkspaces == newNumWorkspaces)
            return false;

        let lostWorkspaces = [];
        if (newNumWorkspaces > oldNumWorkspaces) {
            let w;

            // Assume workspaces are only added at the end
            for (w = oldNumWorkspaces; w < newNumWorkspaces; w++)
                this._workspaces[w] = workspaceManager.get_workspace_by_index(w);

            for (w = oldNumWorkspaces; w < newNumWorkspaces; w++) {
                let workspace = this._workspaces[w];
                workspace._windowAddedId = workspace.connect('window-added', this._queueCheckWorkspaces.bind(this));
                workspace._windowRemovedId = workspace.connect('window-removed', this._windowRemoved.bind(this));
            }

        } else {
            // Assume workspaces are only removed sequentially
            // (e.g. 2,3,4 - not 2,4,7)
            let removedIndex;
            let removedNum = oldNumWorkspaces - newNumWorkspaces;
            for (let w = 0; w < oldNumWorkspaces; w++) {
                let workspace = workspaceManager.get_workspace_by_index(w);
                if (this._workspaces[w] != workspace) {
                    removedIndex = w;
                    break;
                }
            }

            let lostWorkspaces = this._workspaces.splice(removedIndex, removedNum);
            lostWorkspaces.forEach(workspace => {
                workspace.disconnect(workspace._windowAddedId);
                workspace.disconnect(workspace._windowRemovedId);
            });
        }

        this._queueCheckWorkspaces();

        return false;
    }
};

var TilePreview = class {
    constructor() {
        this.actor = new St.Widget();
        global.window_group.add_actor(this.actor);

        this._reset();
        this._showing = false;
    }

    show(window, tileRect, monitorIndex) {
        let windowActor = window.get_compositor_private();
        if (!windowActor)
            return;

        global.window_group.set_child_below_sibling(this.actor, windowActor);

        if (this._rect && this._rect.equal(tileRect))
            return;

        let changeMonitor = (this._monitorIndex == -1 ||
                             this._monitorIndex != monitorIndex);

        this._monitorIndex = monitorIndex;
        this._rect = tileRect;

        let monitor = Main.layoutManager.monitors[monitorIndex];

        this._updateStyle(monitor);

        if (!this._showing || changeMonitor) {
            let monitorRect = new Meta.Rectangle({ x: monitor.x,
                                                   y: monitor.y,
                                                   width: monitor.width,
                                                   height: monitor.height });
            let [, rect] = window.get_frame_rect().intersect(monitorRect);
            this.actor.set_size(rect.width, rect.height);
            this.actor.set_position(rect.x, rect.y);
            this.actor.opacity = 0;
        }

        this._showing = true;
        this.actor.show();
        Tweener.addTween(this.actor,
                         { x: tileRect.x,
                           y: tileRect.y,
                           width: tileRect.width,
                           height: tileRect.height,
                           opacity: 255,
                           time: WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad'
                         });
    }

    hide() {
        if (!this._showing)
            return;

        this._showing = false;
        Tweener.addTween(this.actor,
                         { opacity: 0,
                           time: WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._reset.bind(this)
                         });
    }

    _reset() {
        this.actor.hide();
        this._rect = null;
        this._monitorIndex = -1;
    }

    _updateStyle(monitor) {
        let styles = ['tile-preview'];
        if (this._monitorIndex == Main.layoutManager.primaryIndex)
            styles.push('on-primary');
        if (this._rect.x == monitor.x)
            styles.push('tile-preview-left');
        if (this._rect.x + this._rect.width == monitor.x + monitor.width)
            styles.push('tile-preview-right');

        this.actor.style_class = styles.join(' ');
    }
};

var TouchpadWorkspaceSwitchAction = class {
    constructor(actor, allowedModes) {
        this._allowedModes = allowedModes;
        this._dx = 0;
        this._dy = 0;
        this._enabled = true;
        actor.connect('captured-event', this._handleEvent.bind(this));
	this._touchpadSettings = new Gio.Settings({schema_id: 'org.gnome.desktop.peripherals.touchpad'});
    }

    get enabled() {
        return this._enabled;
    }

    set enabled(enabled) {
        if (this._enabled == enabled)
            return;

        this._enabled = enabled;
        if (!enabled)
            this.emit('cancel');
    }

    _checkActivated() {
        let dir;

        if (this._dy < -MOTION_THRESHOLD)
            dir = Meta.MotionDirection.DOWN;
        else if (this._dy > MOTION_THRESHOLD)
            dir = Meta.MotionDirection.UP;
        else if (this._dx < -MOTION_THRESHOLD)
            dir = Meta.MotionDirection.RIGHT;
        else if (this._dx > MOTION_THRESHOLD)
            dir = Meta.MotionDirection.LEFT;
        else
            return false;

        this.emit('activated', dir);
        return true;
    }

    _handleEvent(actor, event) {
        if (event.type() != Clutter.EventType.TOUCHPAD_SWIPE)
            return Clutter.EVENT_PROPAGATE;

        if (event.get_touchpad_gesture_finger_count() != 4)
            return Clutter.EVENT_PROPAGATE;

        if ((this._allowedModes & Main.actionMode) == 0)
            return Clutter.EVENT_PROPAGATE;

        if (!this._enabled)
            return Clutter.EVENT_PROPAGATE;

        if (event.get_gesture_phase() == Clutter.TouchpadGesturePhase.UPDATE) {
            let [dx, dy] = event.get_gesture_motion_delta();

            // Scale deltas up a bit to make it feel snappier
            this._dx += dx * 2;
	    if(!(this._touchpadSettings.get_boolean('natural-scroll'))) 
		this._dy -= dy * 2;
	    else
		this._dy += dy * 2;
	    
            this.emit('motion', this._dx, this._dy);
        } else {
            if ((event.get_gesture_phase() == Clutter.TouchpadGesturePhase.END && ! this._checkActivated()) ||
                event.get_gesture_phase() == Clutter.TouchpadGesturePhase.CANCEL)
                this.emit('cancel');

            this._dx = 0;
            this._dy = 0;
        }

        return Clutter.EVENT_STOP;
    }
};
Signals.addSignalMethods(TouchpadWorkspaceSwitchAction.prototype);

var WorkspaceSwitchAction = GObject.registerClass({
    Signals: { 'activated': { param_types: [Meta.MotionDirection.$gtype] },
               'motion':    { param_types: [GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
               'cancel':    { param_types: [] }},
}, class WorkspaceSwitchAction extends Clutter.SwipeAction {
    _init(allowedModes) {
        super._init();
        this.set_n_touch_points(4);
        this._swept = false;
        this._allowedModes = allowedModes;

        global.display.connect('grab-op-begin', () => {
            this.cancel();
        });
    }

    vfunc_gesture_prepare(actor) {
        this._swept = false;

        if (!super.vfunc_gesture_prepare(actor))
            return false;

        return (this._allowedModes & Main.actionMode);
    }

    vfunc_gesture_progress(actor) {
        let [x, y] = this.get_motion_coords(0);
        let [xPress, yPress] = this.get_press_coords(0);
        this.emit('motion', x - xPress, y - yPress);
        return true;
    }

    vfunc_gesture_cancel(actor) {
        if (!this._swept)
            this.emit('cancel');
    }

    vfunc_swipe(actor, direction) {
        let [x, y] = this.get_motion_coords(0);
        let [xPress, yPress] = this.get_press_coords(0);
        if (Math.abs(x - xPress) < MOTION_THRESHOLD &&
            Math.abs(y - yPress) < MOTION_THRESHOLD) {
            this.emit('cancel');
            return;
        }

        let dir;

        if (direction & Clutter.SwipeDirection.UP)
            dir = Meta.MotionDirection.DOWN;
        else if (direction & Clutter.SwipeDirection.DOWN)
            dir = Meta.MotionDirection.UP;
        else if (direction & Clutter.SwipeDirection.LEFT)
            dir = Meta.MotionDirection.RIGHT;
        else if (direction & Clutter.SwipeDirection.RIGHT)
            dir = Meta.MotionDirection.LEFT;

        this._swept = true;
        this.emit('activated', dir);
    }
});

var AppSwitchAction = GObject.registerClass({
    Signals: { 'activated': {} },
}, class AppSwitchAction extends Clutter.GestureAction {
    _init() {
        super._init();
        this.set_n_touch_points(3);

        global.display.connect('grab-op-begin', () => {
            this.cancel();
        });
    }

    vfunc_gesture_prepare(actor) {
        if (Main.actionMode != Shell.ActionMode.NORMAL) {
            this.cancel();
            return false;
        }

        return this.get_n_current_points() <= 4;
    }

    vfunc_gesture_begin(actor) {
        // in milliseconds
        const LONG_PRESS_TIMEOUT = 250;

        let nPoints = this.get_n_current_points();
        let event = this.get_last_event (nPoints - 1);

        if (nPoints == 3)
            this._longPressStartTime = event.get_time();
        else if (nPoints == 4) {
            // Check whether the 4th finger press happens after a 3-finger long press,
            // this only needs to be checked on the first 4th finger press
            if (this._longPressStartTime != null &&
                event.get_time() < this._longPressStartTime + LONG_PRESS_TIMEOUT)
                this.cancel();
            else {
                this._longPressStartTime = null;
                this.emit('activated');
            }
        }

        return this.get_n_current_points() <= 4;
    }

    vfunc_gesture_progress(actor) {
        const MOTION_THRESHOLD = 30;

        if (this.get_n_current_points() == 3) {
            for (let i = 0; i < this.get_n_current_points(); i++) {
                let [startX, startY] = this.get_press_coords(i);
                let [x, y] = this.get_motion_coords(i);

                if (Math.abs(x - startX) > MOTION_THRESHOLD ||
                    Math.abs(y - startY) > MOTION_THRESHOLD)
                    return false;
            }

        }

        return true;
    }
});

var ResizePopup = class {
    constructor() {
        this._widget = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        this._label = new St.Label({ style_class: 'resize-popup',
                                     x_align: Clutter.ActorAlign.CENTER,
                                     y_align: Clutter.ActorAlign.CENTER,
                                     x_expand: true, y_expand: true });
        this._widget.add_child(this._label);
        Main.uiGroup.add_actor(this._widget);
    }

    set(rect, displayW, displayH) {
        /* Translators: This represents the size of a window. The first number is
         * the width of the window and the second is the height. */
        let text = _("%d × %d").format(displayW, displayH);
        this._label.set_text(text);

        this._widget.set_position(rect.x, rect.y);
        this._widget.set_size(rect.width, rect.height);
    }

    destroy() {
        this._widget.destroy();
        this._widget = null;
    }
};

var WindowManager = class {
    constructor() {
        this._shellwm =  global.window_manager;

        this._minimizing = [];
        this._unminimizing = [];
        this._mapping = [];
        this._resizing = [];
        this._destroying = [];
        this._movingWindow = null;

        this._dimmedWindows = [];

        this._skippedActors = [];

        this._allowedKeybindings = {};

        this._isWorkspacePrepended = false;

        this._switchData = null;
        this._shellwm.connect('kill-switch-workspace', (shellwm) => {
            if (this._switchData) {
                if (this._switchData.inProgress)
                    this._switchWorkspaceDone(shellwm);
                else if (!this._switchData.gestureActivated)
                    this._finishWorkspaceSwitch(this._switchData);
            }
        });
        this._shellwm.connect('kill-window-effects', (shellwm, actor) => {
            this._minimizeWindowDone(shellwm, actor);
            this._mapWindowDone(shellwm, actor);
            this._destroyWindowDone(shellwm, actor);
            this._sizeChangeWindowDone(shellwm, actor);
        });

        this._shellwm.connect('switch-workspace', this._switchWorkspace.bind(this));
        this._shellwm.connect('show-tile-preview', this._showTilePreview.bind(this));
        this._shellwm.connect('hide-tile-preview', this._hideTilePreview.bind(this));
        this._shellwm.connect('show-window-menu', this._showWindowMenu.bind(this));
        this._shellwm.connect('minimize', this._minimizeWindow.bind(this));
        this._shellwm.connect('unminimize', this._unminimizeWindow.bind(this));
        this._shellwm.connect('size-change', this._sizeChangeWindow.bind(this));
        this._shellwm.connect('size-changed', this._sizeChangedWindow.bind(this));
        this._shellwm.connect('map', this._mapWindow.bind(this));
        this._shellwm.connect('destroy', this._destroyWindow.bind(this));
        this._shellwm.connect('filter-keybinding', this._filterKeybinding.bind(this));
        this._shellwm.connect('confirm-display-change', this._confirmDisplayChange.bind(this));
        this._shellwm.connect('create-close-dialog', this._createCloseDialog.bind(this));
        this._shellwm.connect('create-inhibit-shortcuts-dialog', this._createInhibitShortcutsDialog.bind(this));
        global.display.connect('restacked', this._syncStacking.bind(this));

        this._workspaceSwitcherPopup = null;
        this._tilePreview = null;

        this.allowKeybinding('switch-to-session-1', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-2', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-3', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-4', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-5', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-6', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-7', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-8', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-9', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-10', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-11', Shell.ActionMode.ALL);
        this.allowKeybinding('switch-to-session-12', Shell.ActionMode.ALL);

        this.setCustomKeybindingHandler('switch-to-workspace-left',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-right',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-up',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-down',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-last',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-left',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-right',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-up',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-down',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-1',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-2',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-3',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-4',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-5',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-6',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-7',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-8',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-9',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-10',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-11',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-to-workspace-12',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-1',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-2',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-3',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-4',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-5',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-6',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-7',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-8',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-9',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-10',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-11',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-12',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('move-to-workspace-last',
                                        Shell.ActionMode.NORMAL,
                                        this._showWorkspaceSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-applications',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-group',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-applications-backward',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-group-backward',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-windows',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-windows-backward',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('cycle-windows',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('cycle-windows-backward',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('cycle-group',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('cycle-group-backward',
                                        Shell.ActionMode.NORMAL,
                                        this._startSwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-panels',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW |
                                        Shell.ActionMode.LOCK_SCREEN |
                                        Shell.ActionMode.UNLOCK_SCREEN |
                                        Shell.ActionMode.LOGIN_SCREEN,
                                        this._startA11ySwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-panels-backward',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW |
                                        Shell.ActionMode.LOCK_SCREEN |
                                        Shell.ActionMode.UNLOCK_SCREEN |
                                        Shell.ActionMode.LOGIN_SCREEN,
                                        this._startA11ySwitcher.bind(this));
        this.setCustomKeybindingHandler('switch-monitor',
                                        Shell.ActionMode.NORMAL |
                                        Shell.ActionMode.OVERVIEW,
                                        this._startSwitcher.bind(this));

        this.addKeybinding('pause-resume-tweens',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.NONE,
                           Shell.ActionMode.ALL,
                           this._toggleTweens.bind(this));

        this.addKeybinding('open-application-menu',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.POPUP,
                           this._toggleAppMenu.bind(this));

        this.addKeybinding('toggle-message-tray',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW |
                           Shell.ActionMode.POPUP,
                           this._toggleCalendar.bind(this));

        this.addKeybinding('switch-to-application-1',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-2',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-3',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-4',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-5',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-6',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-7',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-8',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        this.addKeybinding('switch-to-application-9',
                           new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                           Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
                           Shell.ActionMode.NORMAL |
                           Shell.ActionMode.OVERVIEW,
                           this._switchToApplication.bind(this));

        global.display.connect('show-resize-popup', this._showResizePopup.bind(this));
        global.display.connect('show-pad-osd', this._showPadOsd.bind(this));
        global.display.connect('show-osd', (display, monitorIndex, iconName, label) => {
            let icon = Gio.Icon.new_for_string(iconName);
            Main.osdWindowManager.show(monitorIndex, icon, label, null);
        });

        this._gsdWacomProxy = new GsdWacomProxy(Gio.DBus.session, GSD_WACOM_BUS_NAME,
                                                GSD_WACOM_OBJECT_PATH,
                                                (proxy, error) => {
                                                    if (error) {
                                                        log(error.message);
                                                        return;
                                                    }
                                                });

        global.display.connect('pad-mode-switch', (display, pad, group, mode) => {
            let labels = [];

            //FIXME: Fix num buttons
            for (let i = 0; i < 50; i++) {
                let str = display.get_pad_action_label(pad, Meta.PadActionType.BUTTON, i);
                labels.push(str ? str: '');
            }

            if (this._gsdWacomProxy) {
                this._gsdWacomProxy.SetOLEDLabelsRemote(pad.get_device_node(), labels);
            }
        });

        Main.overview.connect('showing', () => {
            for (let i = 0; i < this._dimmedWindows.length; i++)
                this._undimWindow(this._dimmedWindows[i]);
        });
        Main.overview.connect('hiding', () => {
            for (let i = 0; i < this._dimmedWindows.length; i++)
                this._dimWindow(this._dimmedWindows[i]);
        });

        this._windowMenuManager = new WindowMenu.WindowMenuManager();

        if (Main.sessionMode.hasWorkspaces)
            this._workspaceTracker = new WorkspaceTracker(this);

        global.workspace_manager.override_workspace_layout(Meta.DisplayCorner.TOPLEFT,
                                                           false, -1, 1);

        let allowedModes = Shell.ActionMode.NORMAL;
        let gesture = new WorkspaceSwitchAction(allowedModes);
        gesture.connect('motion', this._switchWorkspaceMotion.bind(this));
        gesture.connect('activated', this._actionSwitchWorkspace.bind(this));
        gesture.connect('cancel', this._switchWorkspaceCancel.bind(this));
        global.stage.add_action(gesture);

        // This is not a normal Clutter.GestureAction, doesn't need add_action()
        gesture = new TouchpadWorkspaceSwitchAction(global.stage, allowedModes);
        gesture.connect('motion', this._switchWorkspaceMotion.bind(this));
        gesture.connect('activated', this._actionSwitchWorkspace.bind(this));
        gesture.connect('cancel', this._switchWorkspaceCancel.bind(this));

        gesture = new AppSwitchAction();
        gesture.connect('activated', this._switchApp.bind(this));
        global.stage.add_action(gesture);

        let mode = Shell.ActionMode.ALL & ~Shell.ActionMode.LOCK_SCREEN;
        gesture = new EdgeDragAction.EdgeDragAction(St.Side.BOTTOM, mode);
        gesture.connect('activated', () => {
            Main.keyboard.show(Main.layoutManager.bottomIndex);
        });
        Main.layoutManager.connect('keyboard-visible-changed', (manager, visible) => {
            gesture.cancel();
            gesture.set_enabled(!visible);
        });
        global.stage.add_action(gesture);

        gesture = new EdgeDragAction.EdgeDragAction(St.Side.TOP, mode);
        gesture.connect('activated',  () => {
            let currentWindow = global.display.focus_window;
            if (currentWindow)
                currentWindow.unmake_fullscreen();
        });

        let updateUnfullscreenGesture = () => {
            let currentWindow = global.display.focus_window;
            gesture.enabled = currentWindow && currentWindow.is_fullscreen();
        }

        global.display.connect('notify::focus-window', updateUnfullscreenGesture);
        global.display.connect('in-fullscreen-changed', updateUnfullscreenGesture);

        global.stage.add_action(gesture);
    }

    _showPadOsd(display, device, settings, imagePath, editionMode, monitorIndex) {
        this._currentPadOsd = new PadOsd.PadOsd(device, settings, imagePath, editionMode, monitorIndex);
        this._currentPadOsd.connect('closed', () => { this._currentPadOsd = null });

        return this._currentPadOsd.actor;
    }

    _switchWorkspaceMotion(action, xRel, yRel) {
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();

        if (!this._switchData)
            this._prepareWorkspaceSwitch(activeWorkspace.index(), -1);

        if (yRel < 0 && !this._switchData.surroundings[Meta.MotionDirection.DOWN])
            yRel = 0;
        if (yRel > 0 && !this._switchData.surroundings[Meta.MotionDirection.UP])
            yRel = 0;
        if (xRel < 0 && !this._switchData.surroundings[Meta.MotionDirection.RIGHT])
            xRel = 0;
        if (xRel > 0 && !this._switchData.surroundings[Meta.MotionDirection.LEFT])
            xRel = 0;

        this._switchData.container.set_position(xRel, yRel);
    }

    _switchWorkspaceCancel() {
        if (!this._switchData || this._switchData.inProgress)
            return;
        let switchData = this._switchData;
        this._switchData = null;
        Tweener.addTween(switchData.container,
                         { x: 0,
                           y: 0,
                           time: WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._finishWorkspaceSwitch,
                           onCompleteScope: this,
                           onCompleteParams: [switchData],
                         });
    }

    _actionSwitchWorkspace(action, direction) {
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        let newWs = activeWorkspace.get_neighbor(direction);

        if (newWs == activeWorkspace) {
            this._switchWorkspaceCancel();
        } else {
            this._switchData.gestureActivated = true;
            this.actionMoveWorkspace(newWs);
        }
    }

    _lookupIndex(windows, metaWindow) {
        for (let i = 0; i < windows.length; i++) {
            if (windows[i].metaWindow == metaWindow) {
                return i;
            }
        }
        return -1;
    }

    _switchApp() {
        let windows = global.get_window_actors().filter(actor => {
            let win = actor.metaWindow;
            let workspaceManager = global.workspace_manager;
            let activeWorkspace = workspaceManager.get_active_workspace();
            return (!win.is_override_redirect() &&
                    win.located_on_workspace(activeWorkspace));
        });

        if (windows.length == 0)
            return;

        let focusWindow = global.display.focus_window;
        let nextWindow;

        if (focusWindow == null)
            nextWindow = windows[0].metaWindow;
        else {
            let index = this._lookupIndex (windows, focusWindow) + 1;

            if (index >= windows.length)
                index = 0;

            nextWindow = windows[index].metaWindow;
        }

        Main.activateWindow(nextWindow);
    }

    insertWorkspace(pos) {
        let workspaceManager = global.workspace_manager;

        if (!Meta.prefs_get_dynamic_workspaces())
            return;

        workspaceManager.append_new_workspace(false, global.get_current_time());

        let windows = global.get_window_actors().map(a => a.meta_window);

        // To create a new workspace, we slide all the windows on workspaces
        // below us to the next workspace, leaving a blank workspace for us
        // to recycle.
        windows.forEach(window => {
            // If the window is attached to an ancestor, we don't need/want
            // to move it
            if (window.get_transient_for() != null)
                return;
            // Same for OR windows
            if (window.is_override_redirect())
                return;
            // Sticky windows don't need moving, in fact moving would
            // unstick them
            if (window.on_all_workspaces)
                return;
            // Windows on workspaces below pos don't need moving
            let index = window.get_workspace().index();
            if (index < pos)
                return;
            window.change_workspace_by_index(index + 1, true);
        });

        // If the new workspace was inserted before the active workspace,
        // activate the workspace to which its windows went
        let activeIndex = workspaceManager.get_active_workspace_index();
        if (activeIndex >= pos) {
            let newWs = workspaceManager.get_workspace_by_index(activeIndex + 1);
            this._blockAnimations = true;
            newWs.activate(global.get_current_time());
            this._blockAnimations = false;
        }
    }

    keepWorkspaceAlive(workspace, duration) {
        if (!this._workspaceTracker)
            return;

        this._workspaceTracker.keepWorkspaceAlive(workspace, duration);
    }

    skipNextEffect(actor) {
        this._skippedActors.push(actor);
    }

    setCustomKeybindingHandler(name, modes, handler) {
        if (Meta.keybindings_set_custom_handler(name, handler))
            this.allowKeybinding(name, modes);
    }

    addKeybinding(name, settings, flags, modes, handler) {
        let action = global.display.add_keybinding(name, settings, flags, handler);
        if (action != Meta.KeyBindingAction.NONE)
            this.allowKeybinding(name, modes);
        return action;
    }

    removeKeybinding(name) {
        if (global.display.remove_keybinding(name))
            this.allowKeybinding(name, Shell.ActionMode.NONE);
    }

    allowKeybinding(name, modes) {
        this._allowedKeybindings[name] = modes;
    }

    _shouldAnimate() {
        return !(Main.overview.visible || this._blockAnimations);
    }

    _shouldAnimateActor(actor, types) {
        if (this._removeEffect(this._skippedActors, actor))
            return false;

        if (!this._shouldAnimate())
            return false;

        if (!actor.get_texture())
            return false;

        let type = actor.meta_window.get_window_type();
        return types.indexOf(type) >= 0;
    }

    _removeEffect(list, actor) {
        let idx = list.indexOf(actor);
        if (idx != -1) {
            list.splice(idx, 1);
            return true;
        }
        return false;
    }

    _minimizeWindow(shellwm, actor) {
        let types = [Meta.WindowType.NORMAL,
                     Meta.WindowType.MODAL_DIALOG,
                     Meta.WindowType.DIALOG];
        if (!this._shouldAnimateActor(actor, types)) {
            shellwm.completed_minimize(actor);
            return;
        }

        actor.set_scale(1.0, 1.0);

        this._minimizing.push(actor);

        if (actor.meta_window.is_monitor_sized()) {
            Tweener.addTween(actor,
                         { opacity: 0,
                           time: MINIMIZE_WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._minimizeWindowDone,
                           onCompleteScope: this,
                           onCompleteParams: [shellwm, actor],
                           onOverwrite: this._minimizeWindowOverwritten,
                           onOverwriteScope: this,
                           onOverwriteParams: [shellwm, actor]
                         });
        } else {
            let xDest, yDest, xScale, yScale;
            let [success, geom] = actor.meta_window.get_icon_geometry();
            if (success) {
                xDest = geom.x;
                yDest = geom.y;
                xScale = geom.width / actor.width;
                yScale = geom.height / actor.height;
            } else {
                let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
                if (!monitor) {
                    this._minimizeWindowDone();
                    return;
                }
                xDest = monitor.x;
                yDest = monitor.y;
                if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
                    xDest += monitor.width;
                xScale = 0;
                yScale = 0;
            }

            Tweener.addTween(actor,
                             { scale_x: xScale,
                               scale_y: yScale,
                               x: xDest,
                               y: yDest,
                               time: MINIMIZE_WINDOW_ANIMATION_TIME,
                               transition: 'easeInExpo',
                               onComplete: this._minimizeWindowDone,
                               onCompleteScope: this,
                               onCompleteParams: [shellwm, actor],
                               onOverwrite: this._minimizeWindowOverwritten,
                               onOverwriteScope: this,
                               onOverwriteParams: [shellwm, actor]
                             });
        }
    }

    _minimizeWindowDone(shellwm, actor) {
        if (this._removeEffect(this._minimizing, actor)) {
            Tweener.removeTweens(actor);
            actor.set_scale(1.0, 1.0);
            actor.set_opacity(255);
            actor.set_pivot_point(0, 0);

            shellwm.completed_minimize(actor);
        }
    }

    _minimizeWindowOverwritten(shellwm, actor) {
        if (this._removeEffect(this._minimizing, actor)) {
            shellwm.completed_minimize(actor);
        }
    }

    _unminimizeWindow(shellwm, actor) {
        let types = [Meta.WindowType.NORMAL,
                     Meta.WindowType.MODAL_DIALOG,
                     Meta.WindowType.DIALOG];
        if (!this._shouldAnimateActor(actor, types)) {
            shellwm.completed_unminimize(actor);
            return;
        }

        this._unminimizing.push(actor);

        if (actor.meta_window.is_monitor_sized()) {
            actor.opacity = 0;
            actor.set_scale(1.0, 1.0);
            Tweener.addTween(actor,
                         { opacity: 255,
                           time: MINIMIZE_WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._unminimizeWindowDone,
                           onCompleteScope: this,
                           onCompleteParams: [shellwm, actor],
                           onOverwrite: this._unminimizeWindowOverwritten,
                           onOverwriteScope: this,
                           onOverwriteParams: [shellwm, actor]
                         });
        } else {
            let [success, geom] = actor.meta_window.get_icon_geometry();
            if (success) {
                actor.set_position(geom.x, geom.y);
                actor.set_scale(geom.width / actor.width,
                                geom.height / actor.height);
            } else {
                let monitor = Main.layoutManager.monitors[actor.meta_window.get_monitor()];
                if (!monitor) {
                    actor.show();
                    this._unminimizeWindowDone();
                    return;
                }
                actor.set_position(monitor.x, monitor.y);
                if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
                    actor.x += monitor.width;
                actor.set_scale(0, 0);
            }

            let rect = actor.meta_window.get_frame_rect();
            let [xDest, yDest] = [rect.x, rect.y];

            actor.show();
            Tweener.addTween(actor,
                             { scale_x: 1.0,
                               scale_y: 1.0,
                               x: xDest,
                               y: yDest,
                               time: MINIMIZE_WINDOW_ANIMATION_TIME,
                               transition: 'easeInExpo',
                               onComplete: this._unminimizeWindowDone,
                               onCompleteScope: this,
                               onCompleteParams: [shellwm, actor],
                               onOverwrite: this._unminimizeWindowOverwritten,
                               onOverwriteScope: this,
                               onOverwriteParams: [shellwm, actor]
                             });
        }
    }

    _unminimizeWindowDone(shellwm, actor) {
        if (this._removeEffect(this._unminimizing, actor)) {
            Tweener.removeTweens(actor);
            actor.set_scale(1.0, 1.0);
            actor.set_opacity(255);
            actor.set_pivot_point(0, 0);

            shellwm.completed_unminimize(actor);
        }
    }

    _unminimizeWindowOverwritten(shellwm, actor) {
        if (this._removeEffect(this._unminimizing, actor)) {
            shellwm.completed_unminimize(actor);
        }
    }

    _sizeChangeWindow(shellwm, actor, whichChange, oldFrameRect, oldBufferRect) {
        let types = [Meta.WindowType.NORMAL];
        if (!this._shouldAnimateActor(actor, types)) {
            shellwm.completed_size_change(actor);
            return;
        }

        if (oldFrameRect.width > 0 && oldFrameRect.height > 0)
            this._prepareAnimationInfo(shellwm, actor, oldFrameRect, whichChange);
        else
            shellwm.completed_size_change(actor);
    }

    _prepareAnimationInfo(shellwm, actor, oldFrameRect, change) {
        // Position a clone of the window on top of the old position,
        // while actor updates are frozen.
        let actorContent = Shell.util_get_content_for_window_actor(actor, oldFrameRect);
        let actorClone = new St.Widget({ content: actorContent });
        actorClone.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
        actorClone.set_position(oldFrameRect.x, oldFrameRect.y);
        actorClone.set_size(oldFrameRect.width, oldFrameRect.height);
        Main.uiGroup.add_actor(actorClone);

        if (this._clearAnimationInfo(actor))
            this._shellwm.completed_size_change(actor);

        let destroyId = actor.connect('destroy', () => {
            this._clearAnimationInfo(actor);
        });

        actor.__animationInfo = { clone: actorClone,
                                  oldRect: oldFrameRect,
                                  destroyId: destroyId };
    }

    _sizeChangedWindow(shellwm, actor) {
        if (!actor.__animationInfo)
            return;
        if (this._resizing.indexOf(actor) != -1)
            return;

        let actorClone = actor.__animationInfo.clone;
        let targetRect = actor.meta_window.get_frame_rect();
        let sourceRect = actor.__animationInfo.oldRect;

        let scaleX = targetRect.width / sourceRect.width;
        let scaleY = targetRect.height / sourceRect.height;

        this._resizing.push(actor);

        // Now scale and fade out the clone
        Tweener.addTween(actorClone,
                         { x: targetRect.x,
                           y: targetRect.y,
                           scale_x: scaleX,
                           scale_y: scaleY,
                           opacity: 0,
                           time: WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad'
                         });

        actor.translation_x = -targetRect.x + sourceRect.x;
        actor.translation_y = -targetRect.y + sourceRect.y;

        // Now set scale the actor to size it as the clone.
        actor.scale_x = 1 / scaleX;
        actor.scale_y = 1 / scaleY;

        // Scale it to its actual new size
        Tweener.addTween(actor,
                         { scale_x: 1.0,
                           scale_y: 1.0,
                           translation_x: 0,
                           translation_y: 0,
                           time: WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._sizeChangeWindowDone,
                           onCompleteScope: this,
                           onCompleteParams: [shellwm, actor],
                           onOverwrite: this._sizeChangeWindowOverwritten,
                           onOverwriteScope: this,
                           onOverwriteParams: [shellwm, actor]
                         });

        // Now unfreeze actor updates, to get it to the new size.
        // It's important that we don't wait until the animation is completed to
        // do this, otherwise our scale will be applied to the old texture size.
        shellwm.completed_size_change(actor);
    }

    _clearAnimationInfo(actor) {
        if (actor.__animationInfo) {
            actor.__animationInfo.clone.destroy();
            actor.disconnect(actor.__animationInfo.destroyId);
            delete actor.__animationInfo;
            return true;
        }
        return false;
    }

    _sizeChangeWindowDone(shellwm, actor) {
        if (this._removeEffect(this._resizing, actor)) {
            Tweener.removeTweens(actor);
            actor.scale_x = 1.0;
            actor.scale_y = 1.0;
            actor.translation_x = 0;
            actor.translation_y = 0;
            this._clearAnimationInfo(actor);
        }
    }

    _sizeChangeWindowOverwritten(shellwm, actor) {
        if (this._removeEffect(this._resizing, actor))
            this._clearAnimationInfo(actor);
    }

    _hasAttachedDialogs(window, ignoreWindow) {
        var count = 0;
        window.foreach_transient(win => {
            if (win != ignoreWindow &&
                win.is_attached_dialog() &&
                win.get_transient_for() == window) {
                count++;
                return false;
            }
            return true;
        });
        return count != 0;
    }

    _checkDimming(window, ignoreWindow) {
        let shouldDim = this._hasAttachedDialogs(window, ignoreWindow);

        if (shouldDim && !window._dimmed) {
            window._dimmed = true;
            this._dimmedWindows.push(window);
            this._dimWindow(window);
        } else if (!shouldDim && window._dimmed) {
            window._dimmed = false;
            this._dimmedWindows =
                this._dimmedWindows.filter(win => win != window);
            this._undimWindow(window);
        }
    }

    _dimWindow(window) {
        let actor = window.get_compositor_private();
        if (!actor)
            return;
        let dimmer = getWindowDimmer(actor);
        if (!dimmer)
            return;
        if (this._shouldAnimate())
            Tweener.addTween(dimmer,
                             { dimFactor: 1.0,
                               time: DIM_TIME,
                               transition: 'linear'
                             });
        else
            dimmer.dimFactor = 1.0;
    }

    _undimWindow(window) {
        let actor = window.get_compositor_private();
        if (!actor)
            return;
        let dimmer = getWindowDimmer(actor);
        if (!dimmer)
            return;
        if (this._shouldAnimate())
            Tweener.addTween(dimmer,
                             { dimFactor: 0.0,
                               time: UNDIM_TIME,
                               transition: 'linear' });
        else
            dimmer.dimFactor = 0.0;
    }

    _mapWindow(shellwm, actor) {
        actor._windowType = actor.meta_window.get_window_type();
        actor._notifyWindowTypeSignalId =
            actor.meta_window.connect('notify::window-type', () => {
                let type = actor.meta_window.get_window_type();
                if (type == actor._windowType)
                    return;
                if (type == Meta.WindowType.MODAL_DIALOG ||
                    actor._windowType == Meta.WindowType.MODAL_DIALOG) {
                    let parent = actor.get_meta_window().get_transient_for();
                    if (parent)
                        this._checkDimming(parent);
                }

                actor._windowType = type;
            });
        actor.meta_window.connect('unmanaged', window => {
            let parent = window.get_transient_for();
            if (parent)
                this._checkDimming(parent);
        });

        if (actor.meta_window.is_attached_dialog())
            this._checkDimming(actor.get_meta_window().get_transient_for());

        let types = [Meta.WindowType.NORMAL,
                     Meta.WindowType.DIALOG,
                     Meta.WindowType.MODAL_DIALOG];
        if (!this._shouldAnimateActor(actor, types)) {
            shellwm.completed_map(actor);
            return;
        }

        switch (actor._windowType) {
        case Meta.WindowType.NORMAL:
            actor.set_pivot_point(0.5, 1.0);
            actor.scale_x = 0.01;
            actor.scale_y = 0.05;
            actor.opacity = 0;
            actor.show();
            this._mapping.push(actor);

            Tweener.addTween(actor,
                             { opacity: 255,
                               scale_x: 1,
                               scale_y: 1,
                               time: SHOW_WINDOW_ANIMATION_TIME,
                               transition: 'easeOutExpo',
                               onComplete: this._mapWindowDone,
                               onCompleteScope: this,
                               onCompleteParams: [shellwm, actor],
                               onOverwrite: this._mapWindowOverwrite,
                               onOverwriteScope: this,
                               onOverwriteParams: [shellwm, actor]
                             });
            break;
        case Meta.WindowType.MODAL_DIALOG:
        case Meta.WindowType.DIALOG:
            actor.set_pivot_point(0.5, 0.5);
            actor.scale_y = 0;
            actor.opacity = 0;
            actor.show();
            this._mapping.push(actor);

            Tweener.addTween(actor,
                             { opacity: 255,
                               scale_x: 1,
                               scale_y: 1,
                               time: DIALOG_SHOW_WINDOW_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: this._mapWindowDone,
                               onCompleteScope: this,
                               onCompleteParams: [shellwm, actor],
                               onOverwrite: this._mapWindowOverwrite,
                               onOverwriteScope: this,
                               onOverwriteParams: [shellwm, actor]
                             });
            break;
        default:
            shellwm.completed_map(actor);
            return;
        }
    }

    _mapWindowDone(shellwm, actor) {
        if (this._removeEffect(this._mapping, actor)) {
            Tweener.removeTweens(actor);
            actor.opacity = 255;
            actor.set_pivot_point(0, 0);
            actor.scale_y = 1;
            actor.scale_x = 1;
            actor.translation_y = 0;
            actor.translation_x = 0;
            shellwm.completed_map(actor);
        }
    }

    _mapWindowOverwrite(shellwm, actor) {
        if (this._removeEffect(this._mapping, actor)) {
            shellwm.completed_map(actor);
        }
    }

    _destroyWindow(shellwm, actor) {
        let window = actor.meta_window;
        if (actor._notifyWindowTypeSignalId) {
            window.disconnect(actor._notifyWindowTypeSignalId);
            actor._notifyWindowTypeSignalId = 0;
        }
        if (window._dimmed) {
            this._dimmedWindows =
                this._dimmedWindows.filter(win => win != window);
        }

        if (window.is_attached_dialog())
            this._checkDimming(window.get_transient_for(), window);

        let types = [Meta.WindowType.NORMAL,
                     Meta.WindowType.DIALOG,
                     Meta.WindowType.MODAL_DIALOG];
        if (!this._shouldAnimateActor(actor, types)) {
            shellwm.completed_destroy(actor);
            return;
        }

        switch (actor.meta_window.window_type) {
        case Meta.WindowType.NORMAL:
            actor.set_pivot_point(0.5, 0.5);
            this._destroying.push(actor);

            Tweener.addTween(actor,
                             { opacity: 0,
                               scale_x: 0.8,
                               scale_y: 0.8,
                               time: DESTROY_WINDOW_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: this._destroyWindowDone,
                               onCompleteScope: this,
                               onCompleteParams: [shellwm, actor],
                               onOverwrite: this._destroyWindowDone,
                               onOverwriteScope: this,
                               onOverwriteParams: [shellwm, actor]
                             });
            break;
        case Meta.WindowType.MODAL_DIALOG:
        case Meta.WindowType.DIALOG:
            actor.set_pivot_point(0.5, 0.5);
            this._destroying.push(actor);

            if (window.is_attached_dialog()) {
                let parent = window.get_transient_for();
                actor._parentDestroyId = parent.connect('unmanaged', () => {
                    Tweener.removeTweens(actor);
                    this._destroyWindowDone(shellwm, actor);
                });
            }

            Tweener.addTween(actor,
                             { scale_y: 0,
                               time: DIALOG_DESTROY_WINDOW_ANIMATION_TIME,
                               transition: 'easeOutQuad',
                               onComplete: this._destroyWindowDone,
                               onCompleteScope: this,
                               onCompleteParams: [shellwm, actor],
                               onOverwrite: this._destroyWindowDone,
                               onOverwriteScope: this,
                               onOverwriteParams: [shellwm, actor]
                             });
            break;
        default:
            shellwm.completed_destroy(actor);
            return;
        }
    }

    _destroyWindowDone(shellwm, actor) {
        if (this._removeEffect(this._destroying, actor)) {
            let parent = actor.get_meta_window().get_transient_for();
            if (parent && actor._parentDestroyId) {
                parent.disconnect(actor._parentDestroyId);
                actor._parentDestroyId = 0;
            }
            shellwm.completed_destroy(actor);
        }
    }

    _filterKeybinding(shellwm, binding) {
        if (Main.actionMode == Shell.ActionMode.NONE)
            return true;

        // There's little sense in implementing a keybinding in mutter and
        // not having it work in NORMAL mode; handle this case generically
        // so we don't have to explicitly allow all builtin keybindings in
        // NORMAL mode.
        if (Main.actionMode == Shell.ActionMode.NORMAL &&
            binding.is_builtin())
            return false;

        return !(this._allowedKeybindings[binding.get_name()] & Main.actionMode);
    }

    _syncStacking() {
        if (this._switchData == null)
            return;

        let windows = global.get_window_actors();
        let lastCurSibling = null;
        let lastDirSibling = [];
        for (let i = 0; i < windows.length; i++) {
            if (windows[i].get_parent() == this._switchData.curGroup) {
                this._switchData.curGroup.set_child_above_sibling(windows[i], lastCurSibling);
                lastCurSibling = windows[i];
            } else {
                for (let dir of Object.values(Meta.MotionDirection)) {
                    let info = this._switchData.surroundings[dir];
                    if (!info || windows[i].get_parent() != info.actor)
                        continue;

                    let sibling = lastDirSibling[dir];
                    if (sibling == undefined)
                        sibling = null;

                    info.actor.set_child_above_sibling(windows[i], sibling);
                    lastDirSibling[dir] = windows[i];
                    break;
                }
            }
        }
    }

    _getPositionForDirection(direction) {
        let xDest = 0, yDest = 0;

        if (direction == Meta.MotionDirection.UP ||
            direction == Meta.MotionDirection.UP_LEFT ||
            direction == Meta.MotionDirection.UP_RIGHT)
            yDest = -global.screen_height + Main.panel.height;
        else if (direction == Meta.MotionDirection.DOWN ||
            direction == Meta.MotionDirection.DOWN_LEFT ||
            direction == Meta.MotionDirection.DOWN_RIGHT)
            yDest = global.screen_height - Main.panel.height;

        if (direction == Meta.MotionDirection.LEFT ||
            direction == Meta.MotionDirection.UP_LEFT ||
            direction == Meta.MotionDirection.DOWN_LEFT)
            xDest = -global.screen_width;
        else if (direction == Meta.MotionDirection.RIGHT ||
                 direction == Meta.MotionDirection.UP_RIGHT ||
                 direction == Meta.MotionDirection.DOWN_RIGHT)
            xDest = global.screen_width;

        return [xDest, yDest];
    }

    _prepareWorkspaceSwitch(from, to, direction) {
        if (this._switchData)
            return;

        let wgroup = global.window_group;
        let windows = global.get_window_actors();
        let switchData = {};

        this._switchData = switchData;
        switchData.curGroup = new Clutter.Actor();
        switchData.movingWindowBin = new Clutter.Actor();
        switchData.windows = [];
        switchData.surroundings = {};
        switchData.gestureActivated = false;
        switchData.inProgress = false;

        switchData.container = new Clutter.Actor();
        switchData.container.add_actor(switchData.curGroup);

        wgroup.add_actor(switchData.movingWindowBin);
        wgroup.add_actor(switchData.container);

        let workspaceManager = global.workspace_manager;
        let curWs = workspaceManager.get_workspace_by_index (from);

        for (let dir of Object.values(Meta.MotionDirection)) {
            let ws = null;

            if (to < 0)
                ws = curWs.get_neighbor(dir);
            else if (dir == direction)
                ws = workspaceManager.get_workspace_by_index(to);

            if (ws == null || ws == curWs) {
                switchData.surroundings[dir] = null;
                continue;
            }

            let info = { index: ws.index(),
                         actor: new Clutter.Actor() };
            switchData.surroundings[dir] = info;
            switchData.container.add_actor(info.actor);
            info.actor.raise_top();

            let [x, y] = this._getPositionForDirection(dir);
            info.actor.set_position(x, y);
        }

        switchData.movingWindowBin.raise_top();

        for (let i = 0; i < windows.length; i++) {
            let actor = windows[i];
            let window = actor.get_meta_window();

            if (!window.showing_on_its_workspace())
                continue;

            if (window.is_on_all_workspaces())
                continue;

            let record = { window: actor,
                           parent: actor.get_parent() };

            if (this._movingWindow && window == this._movingWindow) {
                switchData.movingWindow = record;
                switchData.windows.push(switchData.movingWindow);
                actor.reparent(switchData.movingWindowBin);
            } else if (window.get_workspace().index() == from) {
                switchData.windows.push(record);
                actor.reparent(switchData.curGroup);
            } else {
                let visible = false;
                for (let dir of Object.values(Meta.MotionDirection)) {
                    let info = switchData.surroundings[dir];

                    if (!info || info.index != window.get_workspace().index())
                        continue;

                    switchData.windows.push(record);
                    actor.reparent(info.actor);
                    visible = true;
                    break;
                }

                actor.visible = visible;
            }
        }

        for (let i = 0; i < switchData.windows.length; i++) {
            let w = switchData.windows[i];

            w.windowDestroyId = w.window.connect('destroy', () => {
                switchData.windows.splice(switchData.windows.indexOf(w), 1);
            });
        }
    }

    _finishWorkspaceSwitch(switchData) {
        this._switchData = null;

        for (let i = 0; i < switchData.windows.length; i++) {
            let w = switchData.windows[i];

            w.window.disconnect(w.windowDestroyId);
            w.window.reparent(w.parent);

            if (w.window.get_meta_window().get_workspace() !=
                global.workspace_manager.get_active_workspace())
                w.window.hide();
        }
        Tweener.removeTweens(switchData.container);
        switchData.container.destroy();
        switchData.movingWindowBin.destroy();

        this._movingWindow = null;
    }

    _switchWorkspace(shellwm, from, to, direction) {
        if (!Main.sessionMode.hasWorkspaces || !this._shouldAnimate()) {
            shellwm.completed_switch_workspace();
            return;
        }

        // If we come from a gesture, switchData will already be set,
        // and we don't want to overwrite it.
        if (!this._switchData)
            this._prepareWorkspaceSwitch(from, to, direction);

        this._switchData.inProgress = true;

        let [xDest, yDest] = this._getPositionForDirection(direction);

        /* @direction is the direction that the "camera" moves, so the
         * screen contents have to move one screen's worth in the
         * opposite direction.
         */
        xDest = -xDest;
        yDest = -yDest;

        Tweener.addTween(this._switchData.container,
                         { x: xDest,
                           y: yDest,
                           time: WINDOW_ANIMATION_TIME,
                           transition: 'easeOutQuad',
                           onComplete: this._switchWorkspaceDone,
                           onCompleteScope: this,
                           onCompleteParams: [shellwm]
                         });
    }

    _switchWorkspaceDone(shellwm) {
        this._finishWorkspaceSwitch(this._switchData);
        shellwm.completed_switch_workspace();
    }

    _showTilePreview(shellwm, window, tileRect, monitorIndex) {
        if (!this._tilePreview)
            this._tilePreview = new TilePreview();
        this._tilePreview.show(window, tileRect, monitorIndex);
    }

    _hideTilePreview(shellwm) {
        if (!this._tilePreview)
            return;
        this._tilePreview.hide();
    }

    _showWindowMenu(shellwm, window, menu, rect) {
        this._windowMenuManager.showWindowMenuForWindow(window, menu, rect);
    }

    _startSwitcher(display, window, binding) {
        let constructor = null;
        switch (binding.get_name()) {
            case 'switch-applications':
            case 'switch-applications-backward':
            case 'switch-group':
            case 'switch-group-backward':
                constructor = AltTab.AppSwitcherPopup;
                break;
            case 'switch-windows':
            case 'switch-windows-backward':
                constructor = AltTab.WindowSwitcherPopup;
                break;
            case 'cycle-windows':
            case 'cycle-windows-backward':
                constructor = AltTab.WindowCyclerPopup;
                break;
            case 'cycle-group':
            case 'cycle-group-backward':
                constructor = AltTab.GroupCyclerPopup;
                break;
            case 'switch-monitor':
                constructor = SwitchMonitor.SwitchMonitorPopup;
                break;
        }

        if (!constructor)
            return;

        /* prevent a corner case where both popups show up at once */
        if (this._workspaceSwitcherPopup != null)
            this._workspaceSwitcherPopup.destroy();

        let tabPopup = new constructor();

        if (!tabPopup.show(binding.is_reversed(), binding.get_name(), binding.get_mask()))
            tabPopup.destroy();
    }

    _startA11ySwitcher(display, window, binding) {
        Main.ctrlAltTabManager.popup(binding.is_reversed(), binding.get_name(), binding.get_mask());
    }

    _switchToApplication(display, window, binding) {
        let [,,,target] = binding.get_name().split('-');
        let apps = AppFavorites.getAppFavorites().getFavorites();
        let app = apps[target - 1];
        if (app)
            app.activate();
    }

    _toggleAppMenu(display, window, event, binding) {
        Main.panel.toggleAppMenu();
    }

    _toggleCalendar(display, window, event, binding) {
        Main.panel.toggleCalendar();
    }

    _toggleTweens() {
        this._tweensPaused = !this._tweensPaused;
        const OrigTweener = imports.tweener.tweener;
        if (this._tweensPaused)
            OrigTweener.pauseAllTweens();
        else
            OrigTweener.resumeAllTweens();
    }

    _showWorkspaceSwitcher(display, window, binding) {
        let workspaceManager = display.get_workspace_manager();

        if (!Main.sessionMode.hasWorkspaces)
            return;

        if (workspaceManager.n_workspaces == 1)
            return;

        let [action,,,target] = binding.get_name().split('-');
        let newWs;
        let direction;
        let vertical = workspaceManager.layout_rows == -1;
        let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;

        if (action == 'move') {
            // "Moving" a window to another workspace doesn't make sense when
            // it cannot be unstuck, and is potentially confusing if a new
            // workspaces is added at the start/end
            if (window.is_always_on_all_workspaces() ||
                (Meta.prefs_get_workspaces_only_on_primary() &&
                 window.get_monitor() != Main.layoutManager.primaryIndex))
              return;
        }

        if (target == 'last') {
            if (vertical)
                direction = Meta.MotionDirection.DOWN;
            else if (rtl)
                direction = Meta.MotionDirection.LEFT;
            else
                direction = Meta.MotionDirection.RIGHT;
            newWs = workspaceManager.get_workspace_by_index(workspaceManager.n_workspaces - 1);
        } else if (isNaN(target)) {
            // Prepend a new workspace dynamically
            if (workspaceManager.get_active_workspace_index() == 0 &&
                action == 'move' && target == 'up' && this._isWorkspacePrepended == false) {
                this.insertWorkspace(0);
                this._isWorkspacePrepended = true;
            }

            direction = Meta.MotionDirection[target.toUpperCase()];
            newWs = workspaceManager.get_active_workspace().get_neighbor(direction);
        } else if ((target > 0) && (target <= workspaceManager.n_workspaces)) {
            target--;
            newWs = workspaceManager.get_workspace_by_index(target);

            if (workspaceManager.get_active_workspace().index() > target) {
                if (vertical)
                    direction = Meta.MotionDirection.UP;
                else if (rtl)
                    direction = Meta.MotionDirection.RIGHT;
                else
                    direction = Meta.MotionDirection.LEFT;
            } else {
                if (vertical)
                    direction = Meta.MotionDirection.DOWN;
                else if (rtl)
                    direction = Meta.MotionDirection.LEFT;
                else
                    direction = Meta.MotionDirection.RIGHT;
            }
        }

        if (workspaceManager.layout_rows == -1 &&
            direction != Meta.MotionDirection.UP &&
            direction != Meta.MotionDirection.DOWN)
            return;

        if (workspaceManager.layout_columns == -1 &&
            direction != Meta.MotionDirection.LEFT &&
            direction != Meta.MotionDirection.RIGHT)
            return;

        if (action == 'switch')
            this.actionMoveWorkspace(newWs);
        else
            this.actionMoveWindow(window, newWs);

        if (!Main.overview.visible) {
            if (this._workspaceSwitcherPopup == null) {
                this._workspaceTracker.blockUpdates();
                this._workspaceSwitcherPopup = new WorkspaceSwitcherPopup.WorkspaceSwitcherPopup();
                this._workspaceSwitcherPopup.connect('destroy', () => {
                    this._workspaceTracker.unblockUpdates();
                    this._workspaceSwitcherPopup = null;
                    this._isWorkspacePrepended = false;
                });
            }
            this._workspaceSwitcherPopup.display(direction, newWs.index());
        }
    }

    actionMoveWorkspace(workspace) {
        if (!Main.sessionMode.hasWorkspaces)
            return;

        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();

        if (activeWorkspace != workspace)
            workspace.activate(global.get_current_time());
    }

    actionMoveWindow(window, workspace) {
        if (!Main.sessionMode.hasWorkspaces)
            return;

        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();

        if (activeWorkspace != workspace) {
            // This won't have any effect for "always sticky" windows
            // (like desktop windows or docks)

            this._movingWindow = window;
            window.change_workspace(workspace);

            global.display.clear_mouse_mode();
            workspace.activate_with_focus (window, global.get_current_time());
        }
    }

    _confirmDisplayChange() {
        let dialog = new DisplayChangeDialog(this._shellwm);
        dialog.open();
    }

    _createCloseDialog(shellwm, window) {
        return new CloseDialog.CloseDialog(window);
    }

    _createInhibitShortcutsDialog(shellwm, window) {
        return new InhibitShortcutsDialog.InhibitShortcutsDialog(window);
    }

    _showResizePopup(display, show, rect, displayW, displayH) {
        if (show) {
            if (!this._resizePopup)
                this._resizePopup = new ResizePopup();

            this._resizePopup.set(rect, displayW, displayH);
        } else {
            if (this._resizePopup) {
                this._resizePopup.destroy();
                this._resizePopup = null;
            }
        }
    }
};
(uuay)rfkill.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;
const Signals = imports.signals;

const Main = imports.ui.main;
const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const { loadInterfaceXML } = imports.misc.fileUtils;

const BUS_NAME = 'org.gnome.SettingsDaemon.Rfkill';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Rfkill';

const RfkillManagerInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Rfkill');
const RfkillManagerProxy = Gio.DBusProxy.makeProxyWrapper(RfkillManagerInterface);

var RfkillManager = class {
    constructor() {
        this._proxy = new RfkillManagerProxy(Gio.DBus.session, BUS_NAME, OBJECT_PATH,
                                             (proxy, error) => {
                                                 if (error) {
                                                     log(error.message);
                                                     return;
                                                 }
                                                 this._proxy.connect('g-properties-changed',
                                                                     this._changed.bind(this));
                                                 this._changed();
                                             });
    }

    get airplaneMode() {
        return this._proxy.AirplaneMode;
    }

    set airplaneMode(v) {
        this._proxy.AirplaneMode = v;
    }

    get hwAirplaneMode() {
        return this._proxy.HardwareAirplaneMode;
    }

    get shouldShowAirplaneMode() {
        return this._proxy.ShouldShowAirplaneMode;
    }

    _changed() {
        this.emit('airplane-mode-changed');
    }
};
Signals.addSignalMethods(RfkillManager.prototype);

var _manager;
function getRfkillManager() {
    if (_manager != null)
        return _manager;

    _manager = new RfkillManager();
    return _manager;
}

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

        this._manager = getRfkillManager();
        this._manager.connect('airplane-mode-changed', this._sync.bind(this));

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'airplane-mode-symbolic';
        this._indicator.hide();

        // The menu only appears when airplane mode is on, so just
        // statically build it as if it was on, rather than dynamically
        // changing the menu contents.
        this._item = new PopupMenu.PopupSubMenuMenuItem(_("Airplane Mode On"), true);
        this._item.icon.icon_name = 'airplane-mode-symbolic';
        this._offItem = this._item.menu.addAction(_("Turn Off"), () => {
            this._manager.airplaneMode = false;
        });
        this._item.menu.addSettingsAction(_("Network Settings"), 'gnome-network-panel.desktop');
        this.menu.addMenuItem(this._item);

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        this._sessionUpdated();
    }

    _sessionUpdated() {
        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        this.menu.setSensitive(sensitive);
    }

    _sync() {
        let airplaneMode = this._manager.airplaneMode;
        let hwAirplaneMode = this._manager.hwAirplaneMode;
        let showAirplaneMode = this._manager.shouldShowAirplaneMode;

        this._indicator.visible = (airplaneMode && showAirplaneMode);
        this._item.actor.visible = (airplaneMode && showAirplaneMode);
        this._offItem.setSensitive(!hwAirplaneMode);

        if (hwAirplaneMode)
            this._offItem.label.text = _("Use hardware switch to turn off");
        else
            this._offItem.label.text = _("Turn Off");
    }
};
(uuay)accessibility.jsK// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib, GObject, St } = imports.gi;
const Mainloop = imports.mainloop;

const PanelMenu = imports.ui.panelMenu;
const PopupMenu = imports.ui.popupMenu;

const A11Y_SCHEMA                   = 'org.gnome.desktop.a11y';
const KEY_ALWAYS_SHOW               = 'always-show-universal-access-status';

const A11Y_KEYBOARD_SCHEMA          = 'org.gnome.desktop.a11y.keyboard';
const KEY_STICKY_KEYS_ENABLED       = 'stickykeys-enable';
const KEY_BOUNCE_KEYS_ENABLED       = 'bouncekeys-enable';
const KEY_SLOW_KEYS_ENABLED         = 'slowkeys-enable';
const KEY_MOUSE_KEYS_ENABLED        = 'mousekeys-enable';

const APPLICATIONS_SCHEMA           = 'org.gnome.desktop.a11y.applications';

var DPI_FACTOR_LARGE              = 1.25;

const WM_SCHEMA                     = 'org.gnome.desktop.wm.preferences';
const KEY_VISUAL_BELL               = 'visual-bell';

const DESKTOP_INTERFACE_SCHEMA      = 'org.gnome.desktop.interface';
const KEY_GTK_THEME                 = 'gtk-theme';
const KEY_ICON_THEME                = 'icon-theme';
const KEY_TEXT_SCALING_FACTOR       = 'text-scaling-factor';

const HIGH_CONTRAST_THEME           = 'HighContrast';

var ATIndicator = GObject.registerClass(
class ATIndicator extends PanelMenu.Button {
    _init() {
        super._init(0.0, _("Accessibility"));

        this._hbox = new St.BoxLayout({ style_class: 'panel-status-menu-box' });
        this._hbox.add_child(new St.Icon({ style_class: 'system-status-icon',
                                           icon_name: 'preferences-desktop-accessibility-symbolic' }));
        this._hbox.add_child(PopupMenu.arrowIcon(St.Side.BOTTOM));

        this.actor.add_child(this._hbox);

        this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
        this._a11ySettings.connect('changed::' + KEY_ALWAYS_SHOW, this._queueSyncMenuVisibility.bind(this));

        let highContrast = this._buildHCItem();
        this.menu.addMenuItem(highContrast);

        let magnifier = this._buildItem(_("Zoom"), APPLICATIONS_SCHEMA,
                                                   'screen-magnifier-enabled');
        this.menu.addMenuItem(magnifier);

        let textZoom = this._buildFontItem();
        this.menu.addMenuItem(textZoom);

        let screenReader = this._buildItem(_("Screen Reader"), APPLICATIONS_SCHEMA,
                                                               'screen-reader-enabled');
        this.menu.addMenuItem(screenReader);

        let screenKeyboard = this._buildItem(_("Screen Keyboard"), APPLICATIONS_SCHEMA,
                                                                   'screen-keyboard-enabled');
        this.menu.addMenuItem(screenKeyboard);

        let visualBell = this._buildItem(_("Visual Alerts"), WM_SCHEMA, KEY_VISUAL_BELL);
        this.menu.addMenuItem(visualBell);

        let stickyKeys = this._buildItem(_("Sticky Keys"), A11Y_KEYBOARD_SCHEMA, KEY_STICKY_KEYS_ENABLED);
        this.menu.addMenuItem(stickyKeys);

        let slowKeys = this._buildItem(_("Slow Keys"), A11Y_KEYBOARD_SCHEMA, KEY_SLOW_KEYS_ENABLED);
        this.menu.addMenuItem(slowKeys);

        let bounceKeys = this._buildItem(_("Bounce Keys"), A11Y_KEYBOARD_SCHEMA, KEY_BOUNCE_KEYS_ENABLED);
        this.menu.addMenuItem(bounceKeys);

        let mouseKeys = this._buildItem(_("Mouse Keys"), A11Y_KEYBOARD_SCHEMA, KEY_MOUSE_KEYS_ENABLED);
        this.menu.addMenuItem(mouseKeys);

        this._syncMenuVisibility();
    }

    _syncMenuVisibility() {
        this._syncMenuVisibilityIdle = 0;

        let alwaysShow = this._a11ySettings.get_boolean(KEY_ALWAYS_SHOW);
        let items = this.menu._getMenuItems();

        this.actor.visible = alwaysShow || items.some(f => !!f.state);

        return GLib.SOURCE_REMOVE;
    }

    _queueSyncMenuVisibility() {
        if (this._syncMenuVisibilityIdle)
            return;

        this._syncMenuVisibilityIdle = Mainloop.idle_add(this._syncMenuVisibility.bind(this));
        GLib.Source.set_name_by_id(this._syncMenuVisibilityIdle, '[gnome-shell] this._syncMenuVisibility');
    }

    _buildItemExtended(string, initial_value, writable, on_set) {
        let widget = new PopupMenu.PopupSwitchMenuItem(string, initial_value);
        if (!writable)
            widget.actor.reactive = false;
        else
            widget.connect('toggled', item => {
                on_set(item.state);
            });
        return widget;
    }

    _buildItem(string, schema, key) {
        let settings = new Gio.Settings({ schema_id: schema });
        settings.connect('changed::'+key, () => {
            widget.setToggleState(settings.get_boolean(key));

            this._queueSyncMenuVisibility();
        });

        let widget = this._buildItemExtended(string,
            settings.get_boolean(key),
            settings.is_writable(key),
            enabled => settings.set_boolean(key, enabled));
        return widget;
    }

    _buildHCItem() {
        let interfaceSettings = new Gio.Settings({ schema_id: DESKTOP_INTERFACE_SCHEMA });
        interfaceSettings.connect('changed::' + KEY_GTK_THEME, () => {
            let value = interfaceSettings.get_string(KEY_GTK_THEME);
            if (value == HIGH_CONTRAST_THEME) {
                highContrast.setToggleState(true);
            } else {
                highContrast.setToggleState(false);
                gtkTheme = value;
            }

            this._queueSyncMenuVisibility();
        });
        interfaceSettings.connect('changed::' + KEY_ICON_THEME, () => {
            let value = interfaceSettings.get_string(KEY_ICON_THEME);
            if (value != HIGH_CONTRAST_THEME)
                iconTheme = value;
        });

        let gtkTheme = interfaceSettings.get_string(KEY_GTK_THEME);
        let iconTheme = interfaceSettings.get_string(KEY_ICON_THEME);
        let hasHC = (gtkTheme == HIGH_CONTRAST_THEME);
        let highContrast = this._buildItemExtended(
            _("High Contrast"),
            hasHC,
            interfaceSettings.is_writable(KEY_GTK_THEME) &&
            interfaceSettings.is_writable(KEY_ICON_THEME),
            enabled => {
                if (enabled) {
                    interfaceSettings.set_string(KEY_ICON_THEME, HIGH_CONTRAST_THEME);
                    interfaceSettings.set_string(KEY_GTK_THEME, HIGH_CONTRAST_THEME);
                } else if(!hasHC) {
                    interfaceSettings.set_string(KEY_ICON_THEME, iconTheme);
                    interfaceSettings.set_string(KEY_GTK_THEME, gtkTheme);
                } else {
                    interfaceSettings.reset(KEY_ICON_THEME);
                    interfaceSettings.reset(KEY_GTK_THEME);
                }
            });
        return highContrast;
    }

    _buildFontItem() {
        let settings = new Gio.Settings({ schema_id: DESKTOP_INTERFACE_SCHEMA });
        settings.connect('changed::' + KEY_TEXT_SCALING_FACTOR, () => {
            let factor = settings.get_double(KEY_TEXT_SCALING_FACTOR);
            let active = (factor > 1.0);
            widget.setToggleState(active);

            this._queueSyncMenuVisibility();
        });

        let factor = settings.get_double(KEY_TEXT_SCALING_FACTOR);
        let initial_setting = (factor > 1.0);
        let widget = this._buildItemExtended(_("Large Text"),
            initial_setting,
            settings.is_writable(KEY_TEXT_SCALING_FACTOR),
            enabled => {
                if (enabled)
                    settings.set_double(KEY_TEXT_SCALING_FACTOR,
                                        DPI_FACTOR_LARGE);
                else
                    settings.reset(KEY_TEXT_SCALING_FACTOR);
            });
        return widget;
    }
});
(uuay)userWidget.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
//
// A widget showing the user avatar and name

const { Clutter, Gio, GLib, GObject, St } = imports.gi;

const Params = imports.misc.params;

var AVATAR_ICON_SIZE = 64;

// Adapted from gdm/gui/user-switch-applet/applet.c
//
// Copyright (C) 2004-2005 James M. Cape <jcape@ignore-your.tv>.
// Copyright (C) 2008,2009 Red Hat, Inc.

var Avatar = class {
    constructor(user, params) {
        this._user = user;
        params = Params.parse(params, { reactive: false,
                                        iconSize: AVATAR_ICON_SIZE,
                                        styleClass: 'user-icon' });
        this._iconSize = params.iconSize;

        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        this.actor = new St.Bin({ style_class: params.styleClass,
                                  track_hover: params.reactive,
                                  reactive: params.reactive,
                                  width: this._iconSize * scaleFactor,
                                  height: this._iconSize * scaleFactor });

        // Monitor the scaling factor to make sure we recreate the avatar when needed.
        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        themeContext.connect('notify::scale-factor', this.update.bind(this));
    }

    setSensitive(sensitive) {
        this.actor.can_focus = sensitive;
        this.actor.reactive = sensitive;
    }

    update() {
        let iconFile = this._user.get_icon_file();
        if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
            iconFile = null;

        if (iconFile) {
            let file = Gio.File.new_for_path(iconFile);
            this.actor.child = null;
            this.actor.style = `
                background-image: url("${iconFile}");
                background-size: ${this._iconSize}px`;
        } else {
            this.actor.style = null;
            this.actor.child = new St.Icon({ icon_name: 'avatar-default-symbolic',
                                             icon_size: this._iconSize });
        }
    }
};

var UserWidgetLabel = GObject.registerClass(
class UserWidgetLabel extends St.Widget {
    _init(user) {
        super._init({ layout_manager: new Clutter.BinLayout() });

        this._user = user;

        this._realNameLabel = new St.Label({ style_class: 'user-widget-label',
                                             y_align: Clutter.ActorAlign.CENTER });
        this.add_child(this._realNameLabel);

        this._userNameLabel = new St.Label({ style_class: 'user-widget-label',
                                             y_align: Clutter.ActorAlign.CENTER });
        this.add_child(this._userNameLabel);

        this._currentLabel = null;

        this._userLoadedId = this._user.connect('notify::is-loaded', this._updateUser.bind(this));
        this._userChangedId = this._user.connect('changed', this._updateUser.bind(this));
        this._updateUser();

        // We can't override the destroy vfunc because that might be called during
        // object finalization, and we can't call any JS inside a GC finalize callback,
        // so we use a signal, that will be disconnected by GObject the first time
        // the actor is destroyed (which is guaranteed to be as part of a normal
        // destroy() call from JS, possibly from some ancestor)
        this.connect('destroy', this._onDestroy.bind(this));
    }

    _onDestroy() {
        if (this._userLoadedId != 0) {
            this._user.disconnect(this._userLoadedId);
            this._userLoadedId = 0;
        }

        if (this._userChangedId != 0) {
            this._user.disconnect(this._userChangedId);
            this._userChangedId = 0;
        }
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;

        let [minRealNameWidth, minRealNameHeight,
             natRealNameWidth, natRealNameHeight] = this._realNameLabel.get_preferred_size();

        let [minUserNameWidth, minUserNameHeight,
             natUserNameWidth, natUserNameHeight] = this._userNameLabel.get_preferred_size();

        if (natRealNameWidth <= availWidth)
            this._currentLabel = this._realNameLabel;
        else
            this._currentLabel = this._userNameLabel;
        this.label_actor = this._currentLabel;

        let childBox = new Clutter.ActorBox();
        childBox.x1 = 0;
        childBox.y1 = 0;
        childBox.x2 = availWidth;
        childBox.y2 = availHeight;

        this._currentLabel.allocate(childBox, flags);
    }

    vfunc_paint() {
        this._currentLabel.paint();
    }

    _updateUser() {
        if (this._user.is_loaded) {
            this._realNameLabel.text = this._user.get_real_name();
            this._userNameLabel.text = this._user.get_user_name();
        } else {
            this._realNameLabel.text = '';
            this._userNameLabel.text = '';
        }
    }
});

var UserWidget = class {
    constructor(user) {
        this._user = user;

        this.actor = new St.BoxLayout({ style_class: 'user-widget',
                                        vertical: false });
        this.actor.connect('destroy', this._onDestroy.bind(this));

        this._avatar = new Avatar(user);
        this.actor.add_child(this._avatar.actor);

        this._label = new UserWidgetLabel(user);
        this.actor.add_child(this._label);

        this._label.bind_property('label-actor', this.actor, 'label-actor',
                                  GObject.BindingFlags.SYNC_CREATE);

        this._userLoadedId = this._user.connect('notify::is-loaded', this._updateUser.bind(this));
        this._userChangedId = this._user.connect('changed', this._updateUser.bind(this));
        this._updateUser();
    }

    _onDestroy() {
        if (this._userLoadedId != 0) {
            this._user.disconnect(this._userLoadedId);
            this._userLoadedId = 0;
        }

        if (this._userChangedId != 0) {
            this._user.disconnect(this._userChangedId);
            this._userChangedId = 0;
        }
    }

    _updateUser() {
        this._avatar.update();
    }
};
(uuay)dnd.js�r// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

// Time to scale down to maxDragActorSize
var SCALE_ANIMATION_TIME = 0.25;
// Time to animate to original position on cancel
var SNAP_BACK_ANIMATION_TIME = 0.25;
// Time to animate to original position on success
var REVERT_ANIMATION_TIME = 0.75;

var DragMotionResult = {
    NO_DROP:   0,
    COPY_DROP: 1,
    MOVE_DROP: 2,
    CONTINUE:  3
};

var DragState = {
    INIT:      0,
    DRAGGING:  1,
    CANCELLED: 2,
};

var DRAG_CURSOR_MAP = {
    0: Meta.Cursor.DND_UNSUPPORTED_TARGET,
    1: Meta.Cursor.DND_COPY,
    2: Meta.Cursor.DND_MOVE
};

var DragDropResult = {
    FAILURE:  0,
    SUCCESS:  1,
    CONTINUE: 2
};
var dragMonitors = [];

let eventHandlerActor = null;
let currentDraggable = null;

function _getEventHandlerActor() {
    if (!eventHandlerActor) {
        eventHandlerActor = new Clutter.Actor({ width: 0, height: 0 });
        Main.uiGroup.add_actor(eventHandlerActor);
        // We connect to 'event' rather than 'captured-event' because the capturing phase doesn't happen
        // when you've grabbed the pointer.
        eventHandlerActor.connect('event', (actor, event) => {
            return currentDraggable._onEvent(actor, event);
        });
    }
    return eventHandlerActor;
}

function addDragMonitor(monitor) {
    dragMonitors.push(monitor);
}

function removeDragMonitor(monitor) {
    for (let i = 0; i < dragMonitors.length; i++)
        if (dragMonitors[i] == monitor) {
            dragMonitors.splice(i, 1);
            return;
        }
}

var _Draggable = class _Draggable {
    constructor(actor, params) {
        params = Params.parse(params, { manualMode: false,
                                        restoreOnSuccess: false,
                                        dragActorMaxSize: undefined,
                                        dragActorOpacity: undefined });

        this.actor = actor;
        this._dragState = DragState.INIT;

        if (!params.manualMode) {
            this.actor.connect('button-press-event',
                               this._onButtonPress.bind(this));
            this.actor.connect('touch-event',
                               this._onTouchEvent.bind(this));
        }

        this.actor.connect('destroy', () => {
            this._actorDestroyed = true;

            if (this._dragState == DragState.DRAGGING && this._dragCancellable)
                this._cancelDrag(global.get_current_time());
            this.disconnectAll();
        });
        this._onEventId = null;
        this._touchSequence = null;

        this._restoreOnSuccess = params.restoreOnSuccess;
        this._dragActorMaxSize = params.dragActorMaxSize;
        this._dragActorOpacity = params.dragActorOpacity;

        this._buttonDown = false; // The mouse button has been pressed and has not yet been released.
        this._animationInProgress = false; // The drag is over and the item is in the process of animating to its original position (snapping back or reverting).
        this._dragCancellable = true;

        this._eventsGrabbed = false;
        this._capturedEventId = 0;
    }

    _onButtonPress(actor, event) {
        if (event.get_button() != 1)
            return Clutter.EVENT_PROPAGATE;

        if (Tweener.getTweenCount(actor))
            return Clutter.EVENT_PROPAGATE;

        this._buttonDown = true;
        this._grabActor(event.get_device());

        let [stageX, stageY] = event.get_coords();
        this._dragStartX = stageX;
        this._dragStartY = stageY;

        return Clutter.EVENT_PROPAGATE;
    }

    _onTouchEvent(actor, event) {
        // We only handle touch events here on wayland. On X11
        // we do get emulated pointer events, which already works
        // for single-touch cases. Besides, the X11 passive touch grab
        // set up by Mutter will make us see first the touch events
        // and later the pointer events, so it will look like two
        // unrelated series of events, we want to avoid double handling
        // in these cases.
        if (!Meta.is_wayland_compositor())
            return Clutter.EVENT_PROPAGATE;

        if (event.type() != Clutter.EventType.TOUCH_BEGIN ||
            !global.display.is_pointer_emulating_sequence(event.get_event_sequence()))
            return Clutter.EVENT_PROPAGATE;

        if (Tweener.getTweenCount(actor))
            return Clutter.EVENT_PROPAGATE;

        this._buttonDown = true;
        this._grabActor(event.get_device(), event.get_event_sequence());

        let [stageX, stageY] = event.get_coords();
        this._dragStartX = stageX;
        this._dragStartY = stageY;

        return Clutter.EVENT_PROPAGATE;
    }

    _grabDevice(actor, pointer, touchSequence) {
        if (touchSequence)
            pointer.sequence_grab(touchSequence, actor);
        else if (pointer)
            pointer.grab (actor);

        this._grabbedDevice = pointer;
        this._touchSequence = touchSequence;

        this._capturedEventId = global.stage.connect('captured-event', (actor, event) => {
            let device = event.get_device();
            if (device != this._grabbedDevice &&
                device.get_device_type() != Clutter.InputDeviceType.KEYBOARD_DEVICE)
                return Clutter.EVENT_STOP;
            return Clutter.EVENT_PROPAGATE;
        });
    }

    _ungrabDevice() {
        if (this._capturedEventId != 0) {
            global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }

        if (this._touchSequence)
            this._grabbedDevice.sequence_ungrab (this._touchSequence);
        else
            this._grabbedDevice.ungrab();

        this._touchSequence = null;
        this._grabbedDevice = null;
    }

    _grabActor(device, touchSequence) {
        this._grabDevice(this.actor, device, touchSequence);
        this._onEventId = this.actor.connect('event',
                                             this._onEvent.bind(this));
    }

    _ungrabActor() {
        if (!this._onEventId)
            return;

        this._ungrabDevice();
        this.actor.disconnect(this._onEventId);
        this._onEventId = null;
    }

    _grabEvents(device, touchSequence) {
        if (!this._eventsGrabbed) {
            this._eventsGrabbed = Main.pushModal(_getEventHandlerActor());
            if (this._eventsGrabbed)
                this._grabDevice(_getEventHandlerActor(), device, touchSequence);
        }
    }

    _ungrabEvents() {
        if (this._eventsGrabbed) {
            this._ungrabDevice();
            Main.popModal(_getEventHandlerActor());
            this._eventsGrabbed = false;
        }
    }

    _eventIsRelease(event) {
        if (event.type() == Clutter.EventType.BUTTON_RELEASE) {
            let buttonMask = (Clutter.ModifierType.BUTTON1_MASK |
                              Clutter.ModifierType.BUTTON2_MASK |
                              Clutter.ModifierType.BUTTON3_MASK);
            /* We only obey the last button release from the device,
             * other buttons may get pressed/released during the DnD op.
             */
            return (event.get_state() & buttonMask) == 0;
        } else if (event.type() == Clutter.EventType.TOUCH_END) {
            /* For touch, we only obey the pointer emulating sequence */
            return global.display.is_pointer_emulating_sequence(event.get_event_sequence());
        }

        return false;
    }

    _onEvent(actor, event) {
        let device = event.get_device();

        if (this._grabbedDevice &&
            device != this._grabbedDevice &&
            device.get_device_type() != Clutter.InputDeviceType.KEYBOARD_DEVICE)
            return Clutter.EVENT_PROPAGATE;

        // We intercept BUTTON_RELEASE event to know that the button was released in case we
        // didn't start the drag, to drop the draggable in case the drag was in progress, and
        // to complete the drag and ensure that whatever happens to be under the pointer does
        // not get triggered if the drag was cancelled with Esc.
        if (this._eventIsRelease(event)) {
            this._buttonDown = false;
            if (this._dragState == DragState.DRAGGING) {
                return this._dragActorDropped(event);
            } else if ((this._dragActor != null || this._dragState == DragState.CANCELLED) &&
                       !this._animationInProgress) {
                // Drag must have been cancelled with Esc.
                this._dragComplete();
                return Clutter.EVENT_STOP;
            } else {
                // Drag has never started.
                this._ungrabActor();
                return Clutter.EVENT_PROPAGATE;
            }
        // We intercept MOTION event to figure out if the drag has started and to draw
        // this._dragActor under the pointer when dragging is in progress
        } else if (event.type() == Clutter.EventType.MOTION ||
                   (event.type() == Clutter.EventType.TOUCH_UPDATE &&
                    global.display.is_pointer_emulating_sequence(event.get_event_sequence()))) {
            if (this._dragActor && this._dragState == DragState.DRAGGING) {
                return this._updateDragPosition(event);
            } else if (this._dragActor == null && this._dragState != DragState.CANCELLED) {
                return this._maybeStartDrag(event);
            }
        // We intercept KEY_PRESS event so that we can process Esc key press to cancel
        // dragging and ignore all other key presses.
        } else if (event.type() == Clutter.EventType.KEY_PRESS && this._dragState == DragState.DRAGGING) {
            let symbol = event.get_key_symbol();
            if (symbol == Clutter.Escape) {
                this._cancelDrag(event.get_time());
                return Clutter.EVENT_STOP;
            }
        }

        return Clutter.EVENT_PROPAGATE;
    }

    /**
     * fakeRelease:
     *
     * Fake a release event.
     * Must be called if you want to intercept release events on draggable
     * actors for other purposes (for example if you're using
     * PopupMenu.ignoreRelease())
     */
    fakeRelease() {
        this._buttonDown = false;
        this._ungrabActor();
    }

    /**
     * startDrag:
     * @stageX: X coordinate of event
     * @stageY: Y coordinate of event
     * @time: Event timestamp
     *
     * Directly initiate a drag and drop operation from the given actor.
     * This function is useful to call if you've specified manualMode
     * for the draggable.
     */
    startDrag(stageX, stageY, time, sequence, device) {
        if (currentDraggable)
            return;

        if (device == undefined) {
            let event = Clutter.get_current_event();

            if (event)
                device = event.get_device();

            if (device == undefined) {
                let manager = Clutter.DeviceManager.get_default();
                device = manager.get_core_device(Clutter.InputDeviceType.POINTER_DEVICE);
            }
        }

        currentDraggable = this;
        this._dragState = DragState.DRAGGING;

        // Special-case St.Button: the pointer grab messes with the internal
        // state, so force a reset to a reasonable state here
        if (this.actor instanceof St.Button) {
            this.actor.fake_release();
            this.actor.hover = false;
        }

        this.emit('drag-begin', time);
        if (this._onEventId)
            this._ungrabActor();

        this._grabEvents(device, sequence);
        global.display.set_cursor(Meta.Cursor.DND_IN_DRAG);

        this._dragX = this._dragStartX = stageX;
        this._dragY = this._dragStartY = stageY;

        if (this.actor._delegate && this.actor._delegate.getDragActor) {
            this._dragActor = this.actor._delegate.getDragActor();
            Main.uiGroup.add_child(this._dragActor);
            this._dragActor.raise_top();
            Shell.util_set_hidden_from_pick(this._dragActor, true);

            // Drag actor does not always have to be the same as actor. For example drag actor
            // can be an image that's part of the actor. So to perform "snap back" correctly we need
            // to know what was the drag actor source.
            if (this.actor._delegate.getDragActorSource) {
                this._dragActorSource = this.actor._delegate.getDragActorSource();
                // If the user dragged from the source, then position
                // the dragActor over it. Otherwise, center it
                // around the pointer
                let [sourceX, sourceY] = this._dragActorSource.get_transformed_position();
                let x, y;
                if (stageX > sourceX && stageX <= sourceX + this._dragActor.width &&
                    stageY > sourceY && stageY <= sourceY + this._dragActor.height) {
                    x = sourceX;
                    y = sourceY;
                } else {
                    x = stageX - this._dragActor.width / 2;
                    y = stageY - this._dragActor.height / 2;
                }
                this._dragActor.set_position(x, y);
            } else {
                this._dragActorSource = this.actor;
            }
            this._dragOrigParent = undefined;

            this._dragOffsetX = this._dragActor.x - this._dragStartX;
            this._dragOffsetY = this._dragActor.y - this._dragStartY;
        } else {
            this._dragActor = this.actor;

            this._dragActorSource = undefined;
            this._dragOrigParent = this.actor.get_parent();
            this._dragOrigX = this._dragActor.x;
            this._dragOrigY = this._dragActor.y;
            this._dragOrigScale = this._dragActor.scale_x;

            // Set the actor's scale such that it will keep the same
            // transformed size when it's reparented to the uiGroup
            let [scaledWidth, scaledHeight] = this.actor.get_transformed_size();
            this._dragActor.set_scale(scaledWidth / this.actor.width,
                                      scaledHeight / this.actor.height);

            let [actorStageX, actorStageY] = this.actor.get_transformed_position();
            this._dragOffsetX = actorStageX - this._dragStartX;
            this._dragOffsetY = actorStageY - this._dragStartY;

            this._dragOrigParent.remove_actor(this._dragActor);
            Main.uiGroup.add_child(this._dragActor);
            this._dragActor.raise_top();
            Shell.util_set_hidden_from_pick(this._dragActor, true);
        }

        this._dragActorDestroyId = this._dragActor.connect('destroy', () => {
            // Cancel ongoing animation (if any)
            this._finishAnimation();

            this._dragActor = null;
            if (this._dragState == DragState.DRAGGING)
                this._dragState = DragState.CANCELLED;
        });
        this._dragOrigOpacity = this._dragActor.opacity;
        if (this._dragActorOpacity != undefined)
            this._dragActor.opacity = this._dragActorOpacity;

        this._snapBackX = this._dragStartX + this._dragOffsetX;
        this._snapBackY = this._dragStartY + this._dragOffsetY;
        this._snapBackScale = this._dragActor.scale_x;

        if (this._dragActorMaxSize != undefined) {
            let [scaledWidth, scaledHeight] = this._dragActor.get_transformed_size();
            let currentSize = Math.max(scaledWidth, scaledHeight);
            if (currentSize > this._dragActorMaxSize) {
                let scale = this._dragActorMaxSize / currentSize;
                let origScale =  this._dragActor.scale_x;
                let origDragOffsetX = this._dragOffsetX;
                let origDragOffsetY = this._dragOffsetY;

                // The position of the actor changes as we scale
                // around the drag position, but we can't just tween
                // to the final position because that tween would
                // fight with updates as the user continues dragging
                // the mouse; instead we do the position computations in
                // an onUpdate() function.
                Tweener.addTween(this._dragActor,
                                 { scale_x: scale * origScale,
                                   scale_y: scale * origScale,
                                   time: SCALE_ANIMATION_TIME,
                                   transition: 'easeOutQuad',
                                   onUpdate() {
                                       let currentScale = this._dragActor.scale_x / origScale;
                                       this._dragOffsetX = currentScale * origDragOffsetX;
                                       this._dragOffsetY = currentScale * origDragOffsetY;
                                       this._dragActor.set_position(this._dragX + this._dragOffsetX,
                                                                    this._dragY + this._dragOffsetY);
                                   },
                                   onUpdateScope: this });
            }
        }
    }

    _maybeStartDrag(event) {
        let [stageX, stageY] = event.get_coords();

        // See if the user has moved the mouse enough to trigger a drag
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        let threshold = St.Settings.get().drag_threshold * scaleFactor;
        if (!currentDraggable &&
            (Math.abs(stageX - this._dragStartX) > threshold ||
             Math.abs(stageY - this._dragStartY) > threshold)) {
            this.startDrag(stageX, stageY, event.get_time(), this._touchSequence, event.get_device());
            this._updateDragPosition(event);
        }

        return true;
    }

    _pickTargetActor() {
        return this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
                                                            this._dragX, this._dragY);
    }

    _updateDragHover() {
        this._updateHoverId = 0;
        let target = this._pickTargetActor();

        let dragEvent = {
            x: this._dragX,
            y: this._dragY,
            dragActor: this._dragActor,
            source: this.actor._delegate,
            targetActor: target
        };

        let targetActorDestroyHandlerId;
        let handleTargetActorDestroyClosure;
        handleTargetActorDestroyClosure = () => {
            target = this._pickTargetActor();
            dragEvent.targetActor = target;
            targetActorDestroyHandlerId =
                target.connect('destroy', handleTargetActorDestroyClosure);
        };
        targetActorDestroyHandlerId =
            target.connect('destroy', handleTargetActorDestroyClosure);

        for (let i = 0; i < dragMonitors.length; i++) {
            let motionFunc = dragMonitors[i].dragMotion;
            if (motionFunc) {
                let result = motionFunc(dragEvent);
                if (result != DragMotionResult.CONTINUE) {
                    global.display.set_cursor(DRAG_CURSOR_MAP[result]);
                    return GLib.SOURCE_REMOVE;
                }
            }
        }
        dragEvent.targetActor.disconnect(targetActorDestroyHandlerId);

        while (target) {
            if (target._delegate && target._delegate.handleDragOver) {
                let [r, targX, targY] = target.transform_stage_point(this._dragX, this._dragY);
                // We currently loop through all parents on drag-over even if one of the children has handled it.
                // We can check the return value of the function and break the loop if it's true if we don't want
                // to continue checking the parents.
                let result = target._delegate.handleDragOver(this.actor._delegate,
                                                             this._dragActor,
                                                             targX,
                                                             targY,
                                                             0);
                if (result != DragMotionResult.CONTINUE) {
                    global.display.set_cursor(DRAG_CURSOR_MAP[result]);
                    return GLib.SOURCE_REMOVE;
                }
            }
            target = target.get_parent();
        }
        global.display.set_cursor(Meta.Cursor.DND_IN_DRAG);
        return GLib.SOURCE_REMOVE;
    }

    _queueUpdateDragHover() {
        if (this._updateHoverId)
            return;

        this._updateHoverId = GLib.idle_add(GLib.PRIORITY_DEFAULT,
                                            this._updateDragHover.bind(this));
        GLib.Source.set_name_by_id(this._updateHoverId, '[gnome-shell] this._updateDragHover');
    }

    _updateDragPosition(event) {
        let [stageX, stageY] = event.get_coords();
        this._dragX = stageX;
        this._dragY = stageY;
        this._dragActor.set_position(stageX + this._dragOffsetX,
                                     stageY + this._dragOffsetY);

        this._queueUpdateDragHover();
        return true;
    }

    _dragActorDropped(event) {
        let [dropX, dropY] = event.get_coords();
        let target = this._dragActor.get_stage().get_actor_at_pos(Clutter.PickMode.ALL,
                                                                  dropX, dropY);

        // We call observers only once per motion with the innermost
        // target actor. If necessary, the observer can walk the
        // parent itself.
        let dropEvent = {
            dropActor: this._dragActor,
            targetActor: target,
            clutterEvent: event
        };
        for (let i = 0; i < dragMonitors.length; i++) {
            let dropFunc = dragMonitors[i].dragDrop;
            if (dropFunc)
                switch (dropFunc(dropEvent)) {
                    case DragDropResult.FAILURE:
                    case DragDropResult.SUCCESS:
                        return true;
                    case DragDropResult.CONTINUE:
                        continue;
                }
        }

        // At this point it is too late to cancel a drag by destroying
        // the actor, the fate of which is decided by acceptDrop and its
        // side-effects
        this._dragCancellable = false;

        while (target) {
            if (target._delegate && target._delegate.acceptDrop) {
                let [r, targX, targY] = target.transform_stage_point(dropX, dropY);
                if (target._delegate.acceptDrop(this.actor._delegate,
                                                this._dragActor,
                                                targX,
                                                targY,
                                                event.get_time())) {
                    // If it accepted the drop without taking the actor,
                    // handle it ourselves.
                    if (this._dragActor && this._dragActor.get_parent() == Main.uiGroup) {
                        if (this._restoreOnSuccess) {
                            this._restoreDragActor(event.get_time());
                            return true;
                        } else
                            this._dragActor.destroy();
                    }

                    this._dragState = DragState.INIT;
                    global.display.set_cursor(Meta.Cursor.DEFAULT);
                    this.emit('drag-end', event.get_time(), true);
                    this._dragComplete();
                    return true;
                }
            }
            target = target.get_parent();
        }

        this._cancelDrag(event.get_time());

        return true;
    }

    _getRestoreLocation() {
        let x, y, scale;

        if (this._dragActorSource && this._dragActorSource.visible) {
            // Snap the clone back to its source
            [x, y] = this._dragActorSource.get_transformed_position();
            let [sourceScaledWidth, sourceScaledHeight] = this._dragActorSource.get_transformed_size();
            scale = sourceScaledWidth ? this._dragActor.width / sourceScaledWidth : 0;
        } else if (this._dragOrigParent) {
            // Snap the actor back to its original position within
            // its parent, adjusting for the fact that the parent
            // may have been moved or scaled
            let [parentX, parentY] = this._dragOrigParent.get_transformed_position();
            let [parentWidth, parentHeight] = this._dragOrigParent.get_size();
            let [parentScaledWidth, parentScaledHeight] = this._dragOrigParent.get_transformed_size();
            let parentScale = 1.0;
            if (parentWidth != 0)
                parentScale = parentScaledWidth / parentWidth;

            x = parentX + parentScale * this._dragOrigX;
            y = parentY + parentScale * this._dragOrigY;
            scale = this._dragOrigScale * parentScale;
        } else {
            // Snap back actor to its original stage position
            x = this._snapBackX;
            y = this._snapBackY;
            scale = this._snapBackScale;
        }

        return [x, y, scale];
    }

    _cancelDrag(eventTime) {
        this.emit('drag-cancelled', eventTime);
        let wasCancelled = (this._dragState == DragState.CANCELLED);
        this._dragState = DragState.CANCELLED;

        if (this._actorDestroyed || wasCancelled) {
            global.display.set_cursor(Meta.Cursor.DEFAULT);
            if (!this._buttonDown)
                this._dragComplete();
            this.emit('drag-end', eventTime, false);
            if (!this._dragOrigParent && this._dragActor)
                this._dragActor.destroy();

            return;
        }

        let [snapBackX, snapBackY, snapBackScale] = this._getRestoreLocation();

        this._animateDragEnd(eventTime,
                             { x: snapBackX,
                               y: snapBackY,
                               scale_x: snapBackScale,
                               scale_y: snapBackScale,
                               time: SNAP_BACK_ANIMATION_TIME,
                             });
    }

    _restoreDragActor(eventTime) {
        this._dragState = DragState.INIT;
        let [restoreX, restoreY, restoreScale] = this._getRestoreLocation();

        // fade the actor back in at its original location
        this._dragActor.set_position(restoreX, restoreY);
        this._dragActor.set_scale(restoreScale, restoreScale);
        this._dragActor.opacity = 0;

        this._animateDragEnd(eventTime,
                             { time: REVERT_ANIMATION_TIME });
    }

    _animateDragEnd(eventTime, params) {
        this._animationInProgress = true;

        params['opacity']          = this._dragOrigOpacity;
        params['transition']       = 'easeOutQuad';
        params['onComplete']       = this._onAnimationComplete;
        params['onCompleteScope']  = this;
        params['onCompleteParams'] = [this._dragActor, eventTime];

        // start the animation
        Tweener.addTween(this._dragActor, params)
    }

    _finishAnimation() {
        if (!this._animationInProgress)
            return

        this._animationInProgress = false;
        if (!this._buttonDown)
            this._dragComplete();

        global.display.set_cursor(Meta.Cursor.DEFAULT);
    }

    _onAnimationComplete(dragActor, eventTime) {
        if (this._dragOrigParent) {
            Main.uiGroup.remove_child(this._dragActor);
            this._dragOrigParent.add_actor(this._dragActor);
            dragActor.set_scale(this._dragOrigScale, this._dragOrigScale);
            dragActor.set_position(this._dragOrigX, this._dragOrigY);
        } else {
            dragActor.destroy();
        }

        this.emit('drag-end', eventTime, false);
        this._finishAnimation();
    }

    _dragComplete() {
        if (!this._actorDestroyed && this._dragActor)
            Shell.util_set_hidden_from_pick(this._dragActor, false);

        this._ungrabEvents();
        global.sync_pointer();

        if (this._updateHoverId) {
            GLib.source_remove(this._updateHoverId);
            this._updateHoverId = 0;
        }

        if (this._dragActor) {
            this._dragActor.disconnect(this._dragActorDestroyId);
            this._dragActor = null;
        }

        this._dragState = DragState.INIT;
        currentDraggable = null;
    }
};
Signals.addSignalMethods(_Draggable.prototype);

/**
 * makeDraggable:
 * @actor: Source actor
 * @params: (optional) Additional parameters
 *
 * Create an object which controls drag and drop for the given actor.
 *
 * If %manualMode is %true in @params, do not automatically start
 * drag and drop on click
 *
 * If %dragActorMaxSize is present in @params, the drag actor will
 * be scaled down to be no larger than that size in pixels.
 *
 * If %dragActorOpacity is present in @params, the drag actor will
 * will be set to have that opacity during the drag.
 *
 * Note that when the drag actor is the source actor and the drop
 * succeeds, the actor scale and opacity aren't reset; if the drop
 * target wants to reuse the actor, it's up to the drop target to
 * reset these values.
 */
function makeDraggable(actor, params) {
    return new _Draggable(actor, params);
}
(uuay)fingerprint.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Gio = imports.gi.Gio;

const FprintManagerIface = `
<node>
<interface name="net.reactivated.Fprint.Manager">
<method name="GetDefaultDevice">
    <arg type="o" direction="out" />
</method>
</interface>
</node>`;

const FprintManagerInfo = Gio.DBusInterfaceInfo.new_for_xml(FprintManagerIface);

function FprintManager() {
    var self = new Gio.DBusProxy({ g_connection: Gio.DBus.system,
                                   g_interface_name: FprintManagerInfo.name,
                                   g_interface_info: FprintManagerInfo,
                                   g_name: 'net.reactivated.Fprint',
                                   g_object_path: '/net/reactivated/Fprint/Manager',
                                   g_flags: (Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES) });

    try {
        self.init(null);
    } catch(e) {
        log('Failed to connect to Fprint service: ' + e.message);
        return null;
    }

    return self;
}
(uuay)windowMenu.jsm"// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*

const { Meta, St } = imports.gi;

const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;

var WindowMenu = class extends PopupMenu.PopupMenu {
    constructor(window, sourceActor) {
        super(sourceActor, 0, St.Side.TOP);

        this.actor.add_style_class_name('window-menu');

        Main.layoutManager.uiGroup.add_actor(this.actor);
        this.actor.hide();

        this._buildMenu(window);
    }

    _buildMenu(window) {
        let type = window.get_window_type();

        let item;

        item = this.addAction(_("Minimize"), () => {
            window.minimize();
        });
        if (!window.can_minimize())
            item.setSensitive(false);

        if (window.get_maximized()) {
            item = this.addAction(_("Unmaximize"), () => {
                window.unmaximize(Meta.MaximizeFlags.BOTH);
            });
        } else {
            item = this.addAction(_("Maximize"), () => {
                window.maximize(Meta.MaximizeFlags.BOTH);
            });
        }
        if (!window.can_maximize())
            item.setSensitive(false);

        item = this.addAction(_("Move"), event => {
            window.begin_grab_op(Meta.GrabOp.KEYBOARD_MOVING, true, event.get_time());
        });
        if (!window.allows_move())
            item.setSensitive(false);

        item = this.addAction(_("Resize"), event => {
            window.begin_grab_op(Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN, true, event.get_time());
        });
        if (!window.allows_resize())
            item.setSensitive(false);

        if (!window.titlebar_is_onscreen() && type != Meta.WindowType.DOCK && type != Meta.WindowType.DESKTOP) {
            this.addAction(_("Move Titlebar Onscreen"), () => {
                window.shove_titlebar_onscreen();
            });
        }

        item = this.addAction(_("Always on Top"), () => {
            if (window.is_above())
                window.unmake_above();
            else
                window.make_above();
        });
        if (window.is_above())
            item.setOrnament(PopupMenu.Ornament.CHECK);
        if (window.get_maximized() == Meta.MaximizeFlags.BOTH ||
            type == Meta.WindowType.DOCK ||
            type == Meta.WindowType.DESKTOP ||
            type == Meta.WindowType.SPLASHSCREEN)
            item.setSensitive(false);

        if (Main.sessionMode.hasWorkspaces &&
            (!Meta.prefs_get_workspaces_only_on_primary() ||
             window.is_on_primary_monitor())) {
            let isSticky = window.is_on_all_workspaces();

            item = this.addAction(_("Always on Visible Workspace"), () => {
                if (isSticky)
                    window.unstick();
                else
                    window.stick();
            });
            if (isSticky)
                item.setOrnament(PopupMenu.Ornament.CHECK);
            if (window.is_always_on_all_workspaces())
                item.setSensitive(false);

            if (!isSticky) {
                let workspace = window.get_workspace();
                if (workspace != workspace.get_neighbor(Meta.MotionDirection.LEFT)) {
                    this.addAction(_("Move to Workspace Left"), () => {
                        let dir = Meta.MotionDirection.LEFT;
                        window.change_workspace(workspace.get_neighbor(dir));
                    });
                }
                if (workspace != workspace.get_neighbor(Meta.MotionDirection.RIGHT)) {
                    this.addAction(_("Move to Workspace Right"), () => {
                        let dir = Meta.MotionDirection.RIGHT;
                        window.change_workspace(workspace.get_neighbor(dir));
                    });
                }
                if (workspace != workspace.get_neighbor(Meta.MotionDirection.UP)) {
                    this.addAction(_("Move to Workspace Up"), () => {
                        let dir = Meta.MotionDirection.UP;
                        window.change_workspace(workspace.get_neighbor(dir));
                    });
                }
                if (workspace != workspace.get_neighbor(Meta.MotionDirection.DOWN)) {
                    this.addAction(_("Move to Workspace Down"), () => {
                        let dir = Meta.MotionDirection.DOWN;
                        window.change_workspace(workspace.get_neighbor(dir));
                    });
                }

                let { workspaceManager } = global;
                let nWorkspaces = workspaceManager.n_workspaces;
                if (nWorkspaces > 1 && !Meta.prefs_get_dynamic_workspaces()) {
                    item = new PopupMenu.PopupSubMenuMenuItem(_("Move to another workspace"));
                    this.addMenuItem(item);

                    let currentIndex = workspaceManager.get_active_workspace_index();
                    for (let i = 0; i < nWorkspaces; i++) {
                        let index = i;
                        let name = Meta.prefs_get_workspace_name(i);
                        let subitem = item.menu.addAction(name, () => {
                            window.change_workspace_by_index(index, false);
                        });
                        subitem.setSensitive(currentIndex != i);
                    }
                }
            }
        }

        let display = global.display;
        let nMonitors = display.get_n_monitors();
        let monitorIndex = window.get_monitor();
        if (nMonitors > 1 && monitorIndex >= 0) {
            this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

            let dir = Meta.DisplayDirection.UP;
            let upMonitorIndex =
                display.get_monitor_neighbor_index(monitorIndex, dir);
            if (upMonitorIndex != -1) {
                this.addAction(_("Move to Monitor Up"), () => {
                    window.move_to_monitor(upMonitorIndex);
                });
            }

            dir = Meta.DisplayDirection.DOWN;
            let downMonitorIndex =
                display.get_monitor_neighbor_index(monitorIndex, dir);
            if (downMonitorIndex != -1) {
                this.addAction(_("Move to Monitor Down"), () => {
                    window.move_to_monitor(downMonitorIndex);
                });
            }

            dir = Meta.DisplayDirection.LEFT;
            let leftMonitorIndex =
                display.get_monitor_neighbor_index(monitorIndex, dir);
            if (leftMonitorIndex != -1) {
                this.addAction(_("Move to Monitor Left"), () => {
                    window.move_to_monitor(leftMonitorIndex);
                });
            }

            dir = Meta.DisplayDirection.RIGHT;
            let rightMonitorIndex =
                display.get_monitor_neighbor_index(monitorIndex, dir);
            if (rightMonitorIndex != -1) {
                this.addAction(_("Move to Monitor Right"), () => {
                    window.move_to_monitor(rightMonitorIndex);
                });
            }
        }

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());

        item = this.addAction(_("Close"), event => {
            window.delete(event.get_time());
        });
        if (!window.can_close())
            item.setSensitive(false);
    }
};

var WindowMenuManager = class {
    constructor() {
        this._manager = new PopupMenu.PopupMenuManager({ actor: Main.layoutManager.dummyCursor });

        this._sourceActor = new St.Widget({ reactive: true, visible: false });
        this._sourceActor.connect('button-press-event', () => {
            this._manager.activeMenu.toggle();
        });
        Main.uiGroup.add_actor(this._sourceActor);
    }

    showWindowMenuForWindow(window, type, rect) {
        if (type != Meta.WindowMenuType.WM)
            throw new Error('Unsupported window menu type');
        let menu = new WindowMenu(window, this._sourceActor);

        this._manager.addMenu(menu);
        this._manager.ignoreRelease();

        menu.connect('activate', () => {
            window.check_alive(global.get_current_time());
        });
        let destroyId = window.connect('unmanaged', () => {
            menu.close();
        });

        this._sourceActor.set_size(Math.max(1, rect.width), Math.max(1, rect.height));
        this._sourceActor.set_position(rect.x, rect.y);
        this._sourceActor.show();

        menu.open(BoxPointer.PopupAnimation.NONE);
        menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
        menu.connect('open-state-changed', (menu_, isOpen) => {
            if (isOpen)
                return;

            this._sourceActor.hide();
            menu.destroy();
            window.disconnect(destroyId);
        });
    }
};
(uuay)animation.jsg// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GLib, Gio, St } = imports.gi;
const Mainloop = imports.mainloop;

const Tweener = imports.ui.tweener;

var ANIMATED_ICON_UPDATE_TIMEOUT = 16;
var SPINNER_ANIMATION_TIME = 0.3;
var SPINNER_ANIMATION_DELAY = 1.0;

var Animation = class {
    constructor(file, width, height, speed) {
        this.actor = new St.Bin();
        this.actor.set_size(width, height);
        this.actor.connect('destroy', this._onDestroy.bind(this));
        this.actor.connect('notify::size', this._syncAnimationSize.bind(this));
        this.actor.connect('resource-scale-changed',
            this._loadFile.bind(this, file, width, height));

        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        this._scaleChangedId = themeContext.connect('notify::scale-factor',
            this._loadFile.bind(this, file, width, height));

        this._speed = speed;

        this._isLoaded = false;
        this._isPlaying = false;
        this._timeoutId = 0;
        this._frame = 0;

        this._loadFile(file, width, height);
    }

    play() {
        if (this._isLoaded && this._timeoutId == 0) {
            if (this._frame == 0)
                this._showFrame(0);

            this._timeoutId = GLib.timeout_add(GLib.PRIORITY_LOW, this._speed, this._update.bind(this));
            GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._update');
        }

        this._isPlaying = true;
    }

    stop() {
        if (this._timeoutId > 0) {
            Mainloop.source_remove(this._timeoutId);
            this._timeoutId = 0;
        }

        this._isPlaying = false;
    }

    _loadFile(file, width, height) {
        let [validResourceScale, resourceScale] = this.actor.get_resource_scale();

        this._isLoaded = false;
        this.actor.destroy_all_children();

        if (!validResourceScale)
            return;

        let texture_cache = St.TextureCache.get_default();
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        this._animations = texture_cache.load_sliced_image(file, width, height,
                                                           scaleFactor, resourceScale,
                                                           this._animationsLoaded.bind(this));
        this.actor.set_child(this._animations);
    }

    _showFrame(frame) {
        let oldFrameActor = this._animations.get_child_at_index(this._frame);
        if (oldFrameActor)
            oldFrameActor.hide();

        this._frame = (frame % this._animations.get_n_children());

        let newFrameActor = this._animations.get_child_at_index(this._frame);
        if (newFrameActor)
            newFrameActor.show();
    }

    _update() {
        this._showFrame(this._frame + 1);
        return GLib.SOURCE_CONTINUE;
    }

    _syncAnimationSize() {
        if (!this._isLoaded)
            return;

        let [width, height] = this.actor.get_size();

        for (let i = 0; i < this._animations.get_n_children(); ++i)
            this._animations.get_child_at_index(i).set_size(width, height);
    }

    _animationsLoaded() {
        this._isLoaded = this._animations.get_n_children() > 0;

        this._syncAnimationSize();

        if (this._isPlaying)
            this.play();
    }

    _onDestroy() {
        this.stop();

        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        if (this._scaleChangedId)
            themeContext.disconnect(this._scaleChangedId);
        this._scaleChangedId = 0;
    }
};

var AnimatedIcon = class extends Animation {
    constructor(file, size) {
        super(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
    }
};

var Spinner = class extends AnimatedIcon {
    constructor(size, animate=false) {
        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
        super(file, size);

        this.actor.opacity = 0;
        this._animate = animate;
    }

    _onDestroy() {
        this._animate = false;
        super._onDestroy();
    }

    play() {
        Tweener.removeTweens(this.actor);

        if (this._animate) {
            super.play();
            Tweener.addTween(this.actor, {
                opacity: 255,
                delay: SPINNER_ANIMATION_DELAY,
                time: SPINNER_ANIMATION_TIME,
                transition: 'linear'
            });
        } else {
            this.actor.opacity = 255;
            super.play();
        }
    }

    stop() {
        Tweener.removeTweens(this.actor);

        if (this._animate) {
            Tweener.addTween(this.actor, {
                opacity: 0,
                time: SPINNER_ANIMATION_TIME,
                transition: 'linear',
                onComplete: () => {
                    super.stop();
                }
            });
        } else {
            this.actor.opacity = 0;
            super.stop();
        }
    }
};
(uuay)tweener.js�!// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, Shell, St } = imports.gi;
const Signals = imports.signals;
const Tweener = imports.tweener.tweener;

// This is a wrapper around imports.tweener.tweener that adds a bit of
// Clutter integration. If the tweening target is a Clutter.Actor, then
// the tweenings will automatically be removed if the actor is destroyed.

// ActionScript Tweener methods that imports.tweener.tweener doesn't
// currently implement: getTweens, getVersion, registerTransition,
// setTimeScale, updateTime.

// imports.tweener.tweener methods that we don't re-export:
// pauseAllTweens, removeAllTweens, resumeAllTweens. (It would be hard
// to clean up properly after removeAllTweens, and also, any code that
// calls any of these is almost certainly wrong anyway, because they
// affect the entire application.)

// Called from Main.start
function init() {
    Tweener.setFrameTicker(new ClutterFrameTicker());
}


function addCaller(target, tweeningParameters) {
    _wrapTweening(target, tweeningParameters);
    Tweener.addCaller(target, tweeningParameters);
}

function addTween(target, tweeningParameters) {
    _wrapTweening(target, tweeningParameters);
    Tweener.addTween(target, tweeningParameters);
}

function _wrapTweening(target, tweeningParameters) {
    let state = _getTweenState(target);

    if (!state.destroyedId) {
        if (target instanceof Clutter.Actor) {
            state.actor = target;
            state.destroyedId = target.connect('destroy', _actorDestroyed);
        } else if (target.actor && target.actor instanceof Clutter.Actor) {
            state.actor = target.actor;
            state.destroyedId = target.actor.connect('destroy', () => { _actorDestroyed(target); });
        }
    }

    if (!St.Settings.get().enable_animations) {
        tweeningParameters['time'] = 0.000001;
        tweeningParameters['delay'] = 0.000001;
    }

    _addHandler(target, tweeningParameters, 'onComplete', _tweenCompleted);
}

function _getTweenState(target) {
    // If we were paranoid, we could keep a plist mapping targets to
    // states... but we're not that paranoid.
    if (!target.__ShellTweenerState)
        target.__ShellTweenerState = {};
    return target.__ShellTweenerState;
}

function _ensureHandlers(target) {
    if (!target.__ShellTweenerHandlers)
        target.__ShellTweenerHandlers = {};
    return target.__ShellTweenerHandlers;
}

function _resetTweenState(target) {
    let state = target.__ShellTweenerState;

    if (state) {
        if (state.destroyedId) {
            state.actor.disconnect(state.destroyedId);
            delete state.destroyedId;
        }
    }

    _removeHandler(target, 'onComplete', _tweenCompleted);
    target.__ShellTweenerState = {};
}

function _addHandler(target, params, name, handler) {
    let wrapperNeeded = false;
    let tweenerHandlers = _ensureHandlers(target);

    if (!(name in tweenerHandlers)) {
        tweenerHandlers[name] = [];
        wrapperNeeded = true;
    }

    let handlers = tweenerHandlers[name];
    handlers.push(handler);

    if (wrapperNeeded) {
        if (params[name]) {
            let oldHandler = params[name];
            let oldScope = params[name + 'Scope'];
            let oldParams = params[name + 'Params'];
            let eventScope = oldScope ? oldScope : target;

            params[name] = () => {
                oldHandler.apply(eventScope, oldParams);
                handlers.forEach((h) => h(target));
            };
        } else {
            params[name] = () => { handlers.forEach((h) => h(target)); };
        }
    }
}

function _removeHandler(target, name, handler) {
    let tweenerHandlers = _ensureHandlers(target);

    if (name in tweenerHandlers) {
        let handlers = tweenerHandlers[name];
        let handlerIndex = handlers.indexOf(handler);

        while (handlerIndex > -1) {
            handlers.splice(handlerIndex, 1);
            handlerIndex = handlers.indexOf(handler);
        }
    }
}

function _actorDestroyed(target) {
    _resetTweenState(target);
    Tweener.removeTweens(target);
}

function _tweenCompleted(target) {
    if (!isTweening(target))
        _resetTweenState(target);
}

function getTweenCount(scope) {
    return Tweener.getTweenCount(scope);
}

// imports.tweener.tweener doesn't provide this method (which exists
// in the ActionScript version) but it's easy to implement.
function isTweening(scope) {
    return Tweener.getTweenCount(scope) != 0;
}

function removeTweens(scope) {
    if (Tweener.removeTweens.apply(null, arguments)) {
        // If we just removed the last active tween, clean up
        if (Tweener.getTweenCount(scope) == 0)
            _tweenCompleted(scope);
        return true;
    } else
        return false;
}

function pauseTweens() {
    return Tweener.pauseTweens.apply(null, arguments);
}

function resumeTweens() {
    return Tweener.resumeTweens.apply(null, arguments);
}


function registerSpecialProperty(name, getFunction, setFunction,
                                 parameters, preProcessFunction) {
    Tweener.registerSpecialProperty(name, getFunction, setFunction,
                                    parameters, preProcessFunction);
}

function registerSpecialPropertyModifier(name, modifyFunction, getFunction) {
    Tweener.registerSpecialPropertyModifier(name, modifyFunction, getFunction);
}

function registerSpecialPropertySplitter(name, splitFunction, parameters) {
    Tweener.registerSpecialPropertySplitter(name, splitFunction, parameters);
}


// The 'FrameTicker' object is an object used to feed new frames to
// Tweener so it can update values and redraw. The default frame
// ticker for Tweener just uses a simple timeout at a fixed frame rate
// and has no idea of "catching up" by dropping frames.
//
// We substitute it with custom frame ticker here that connects
// Tweener to a Clutter.TimeLine. Now, Clutter.Timeline itself isn't a
// whole lot more sophisticated than a simple timeout at a fixed frame
// rate, but at least it knows how to drop frames. (See
// HippoAnimationManager for a more sophisticated view of continous
// time updates; even better is to pay attention to the vertical
// vblank and sync to that when possible.)
//
var ClutterFrameTicker = class {
    constructor() {
        // We don't have a finite duration; tweener will tell us to stop
        // when we need to stop, so use 1000 seconds as "infinity", and
        // set the timeline to loop. Doing this means we have to track
        // time ourselves, since clutter timeline's time will cycle
        // instead of strictly increase.
        this._timeline = new Clutter.Timeline({ duration: 1000*1000 });
        this._timeline.set_loop(true);
        this._startTime = -1;
        this._currentTime = -1;

        this._timeline.connect('new-frame', (timeline, frame) => {
            this._onNewFrame(frame);
        });

        let perf_log = Shell.PerfLog.get_default();
        perf_log.define_event("tweener.framePrepareStart",
                              "Start of a new animation frame",
                              "");
        perf_log.define_event("tweener.framePrepareDone",
                              "Finished preparing frame",
                              "");
    }

    get FRAME_RATE() {
        return 60;
    }

    _onNewFrame(frame) {
        // If there is a lot of setup to start the animation, then
        // first frame number we get from clutter might be a long ways
        // into the animation (or the animation might even be done).
        // That looks bad, so we always start at the first frame of the
        // animation then only do frame dropping from there.
        if (this._startTime < 0)
            this._startTime = GLib.get_monotonic_time() / 1000.0;

        // currentTime is in milliseconds
        let perf_log = Shell.PerfLog.get_default();
        this._currentTime = GLib.get_monotonic_time() / 1000.0 - this._startTime;
        perf_log.event("tweener.framePrepareStart");
        this.emit('prepare-frame');
        perf_log.event("tweener.framePrepareDone");
    }

    getTime() {
        return this._currentTime;
    }

    start() {
        if (St.get_slow_down_factor() > 0)
            Tweener.setTimeScale(1 / St.get_slow_down_factor());
        this._timeline.start();
        global.begin_work();
    }

    stop() {
        this._timeline.stop();
        this._startTime = -1;
        this._currentTime = -1;
        global.end_work();
    }
};
Signals.addSignalMethods(ClutterFrameTicker.prototype);
(uuay)thunderbolt.jse&// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

// the following is a modified version of bolt/contrib/js/client.js

const { Gio, GLib, Polkit, Shell } = imports.gi;
const Signals = imports.signals;

const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const PanelMenu = imports.ui.panelMenu;

const { loadInterfaceXML } = imports.misc.fileUtils;

/* Keep in sync with data/org.freedesktop.bolt.xml */

const BoltClientInterface = loadInterfaceXML('org.freedesktop.bolt1.Manager');
const BoltDeviceInterface = loadInterfaceXML('org.freedesktop.bolt1.Device');

const BoltDeviceProxy = Gio.DBusProxy.makeProxyWrapper(BoltDeviceInterface);

/*  */

var Status = {
    DISCONNECTED: 'disconnected',
    CONNECTING: 'connecting',
    CONNECTED: 'connected',
    AUTHORIZING: 'authorizing',
    AUTH_ERROR: 'auth-error',
    AUTHORIZED: 'authorized'
};

var Policy = {
    DEFAULT: 'default',
    MANUAL: 'manual',
    AUTO: 'auto'
};

var AuthCtrl = {
    NONE: 'none',
};

var AuthMode = {
    DISABLED: 'disabled',
    ENABLED: 'enabled'
};

const BOLT_DBUS_CLIENT_IFACE = 'org.freedesktop.bolt1.Manager';
const BOLT_DBUS_NAME = 'org.freedesktop.bolt';
const BOLT_DBUS_PATH = '/org/freedesktop/bolt';

var Client = class {
    constructor() {
	this._proxy = null;
        let nodeInfo = Gio.DBusNodeInfo.new_for_xml(BoltClientInterface);
        Gio.DBusProxy.new(Gio.DBus.system,
                          Gio.DBusProxyFlags.DO_NOT_AUTO_START,
                          nodeInfo.lookup_interface(BOLT_DBUS_CLIENT_IFACE),
                          BOLT_DBUS_NAME,
                          BOLT_DBUS_PATH,
                          BOLT_DBUS_CLIENT_IFACE,
                          null,
                          this._onProxyReady.bind(this));

	this.probing = false;
    }

    _onProxyReady(o, res) {
        try {
	    this._proxy = Gio.DBusProxy.new_finish(res);
        } catch(e) {
	    log('error creating bolt proxy: %s'.format(e.message));
            return;
        }
	this._propsChangedId = this._proxy.connect('g-properties-changed', this._onPropertiesChanged.bind(this));
	this._deviceAddedId = this._proxy.connectSignal('DeviceAdded', this._onDeviceAdded.bind(this));

	this.probing = this._proxy.Probing;
	if (this.probing)
	    this.emit('probing-changed', this.probing);

    }

    _onPropertiesChanged(proxy, properties) {
        let unpacked = properties.deep_unpack();
        if (!('Probing' in unpacked))
	    return;

	this.probing = this._proxy.Probing;
	this.emit('probing-changed', this.probing);
    }

    _onDeviceAdded(proxy, emitter, params) {
	let [path] = params;
	let device = new BoltDeviceProxy(Gio.DBus.system,
					 BOLT_DBUS_NAME,
					 path);
	this.emit('device-added', device);
    }

    /* public methods */
    close() {
        if (!this._proxy)
            return;

	this._proxy.disconnectSignal(this._deviceAddedId);
	this._proxy.disconnect(this._propsChangedId);
	this._proxy = null;
    }

    enrollDevice(id, policy, callback) {
	this._proxy.EnrollDeviceRemote(id, policy, AuthCtrl.NONE,
                                       (res, error) => {
	    if (error) {
		Gio.DBusError.strip_remote_error(error);
		callback(null, error);
		return;
	    }

	    let [path] = res;
	    let device = new BoltDeviceProxy(Gio.DBus.system,
					     BOLT_DBUS_NAME,
					     path);
	    callback(device, null);
	});
    }

    get authMode () {
        return this._proxy.AuthMode;
    }
};
Signals.addSignalMethods(Client.prototype);

/* helper class to automatically authorize new devices */
var AuthRobot = class {
    constructor(client) {
	this._client = client;

	this._devicesToEnroll = [];
	this._enrolling = false;

	this._client.connect('device-added', this._onDeviceAdded.bind(this));
    }

    close() {
	this.disconnectAll();
	this._client = null;
    }

    /* the "device-added" signal will be emitted by boltd for every
     * device that is not currently stored in the database. We are
     * only interested in those devices, because all known devices
     * will be handled by the user himself */
    _onDeviceAdded(cli, dev) {
	if (dev.Status !== Status.CONNECTED)
	    return;

        /* check if authorization is enabled in the daemon. if not
         * we won't even bother authorizing, because we will only
         * get an error back. The exact contents of AuthMode might 
         * change in the future, but must contain AuthMode.ENABLED
         * if it is enabled. */
        if (!cli.authMode.split('|').includes(AuthMode.ENABLED))
            return;
        
	/* check if we should enroll the device */
	let res = [false];
	this.emit('enroll-device', dev, res);
	if (res[0] !== true)
	    return;

	/* ok, we should authorize the device, add it to the back
	 * of the list  */
	this._devicesToEnroll.push(dev);
	this._enrollDevices();
    }

    /* The enrollment queue:
     *   - new devices will be added to the end of the array.
     *   - an idle callback will be scheduled that will keep
     *     calling itself as long as there a devices to be
     *     enrolled.
     */
    _enrollDevices() {
	if (this._enrolling)
	    return;

	this._enrolling = true;
	GLib.idle_add(GLib.PRIORITY_DEFAULT,
		      this._enrollDevicesIdle.bind(this));
    }

    _onEnrollDone(device, error) {
	if (error)
	    this.emit('enroll-failed', device, error);

	/* TODO: scan the list of devices to be authorized for children
	 *  of this device and remove them (and their children and
	 *  their children and ....) from the device queue
	 */
	this._enrolling = this._devicesToEnroll.length > 0;

	if (this._enrolling)
	    GLib.idle_add(GLib.PRIORITY_DEFAULT,
			  this._enrollDevicesIdle.bind(this));
    }

    _enrollDevicesIdle() {
	let devices = this._devicesToEnroll;

	let dev = devices.shift();
	if (dev === undefined)
	    return GLib.SOURCE_REMOVE;

	this._client.enrollDevice(dev.Uid,
				  Policy.DEFAULT,
				  this._onEnrollDone.bind(this));
	return GLib.SOURCE_REMOVE;
    }
};
Signals.addSignalMethods(AuthRobot.prototype);

/* eof client.js  */

var Indicator = class extends PanelMenu.SystemIndicator {
    constructor() {
        super();

	this._indicator = this._addIndicator();
        this._indicator.icon_name = 'thunderbolt-symbolic';

	this._client = new Client();
	this._client.connect('probing-changed', this._onProbing.bind(this));

	this._robot =  new AuthRobot(this._client);

	this._robot.connect('enroll-device', this._onEnrollDevice.bind(this));
	this._robot.connect('enroll-failed', this._onEnrollFailed.bind(this));

	Main.sessionMode.connect('updated', this._sync.bind(this));
        this._sync();

	this._source = null;
        this._perm = null;

        Polkit.Permission.new('org.freedesktop.bolt.enroll', null, null, (source, res) => {
            try {
                this._perm = Polkit.Permission.new_finish(res);
            } catch (e) {
                log('Failed to get PolKit permission: %s'.format(e.toString()));
            }
        });
    }

    _onDestroy() {
        this._robot.close();
	this._client.close();
    }

    _ensureSource() {
        if (!this._source) {
            this._source = new MessageTray.Source(_("Thunderbolt"),
                                                  'thunderbolt-symbolic');
            this._source.connect('destroy', () => { this._source = null; });

            Main.messageTray.add(this._source);
        }

        return this._source;
    }

    _notify(title, body) {
        if (this._notification)
            this._notification.destroy();

        let source = this._ensureSource();

	this._notification = new MessageTray.Notification(source, title, body);
	this._notification.setUrgency(MessageTray.Urgency.HIGH);
        this._notification.connect('destroy', () => {
            this._notification = null;
        });
        this._notification.connect('activated', () => {
            let app = Shell.AppSystem.get_default().lookup_app('gnome-thunderbolt-panel.desktop');
            if (app)
                app.activate();
        });
        this._source.notify(this._notification);
    }

    /* Session callbacks */
    _sync() {
        let active = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
	this._indicator.visible = active && this._client.probing;
    }

    /* Bolt.Client callbacks */
    _onProbing(cli, probing) {
	if (probing)
	    this._indicator.icon_name = 'thunderbolt-acquiring-symbolic';
	else
	    this._indicator.icon_name = 'thunderbolt-symbolic';

        this._sync();
    }

    /* AuthRobot callbacks */
    _onEnrollDevice(obj, device, policy) {
        /* only authorize new devices when in an unlocked user session */
	let unlocked = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        /* and if we have the permission to do so, otherwise we trigger a PolKit dialog */
        let allowed = this._perm && this._perm.allowed;

        let auth = unlocked && allowed;
	policy[0] = auth;

        log(`thunderbolt: [${device.Name}] auto enrollment: ${auth ? 'yes' : 'no'} (allowed: ${allowed ? 'yes' : 'no'})`);

	if (auth)
	    return; /* we are done */

        if (!unlocked) {
	    const title = _("Unknown Thunderbolt device");
	    const body = _("New device has been detected while you were away. Please disconnect and reconnect the device to start using it.");
	    this._notify(title, body);
        } else {
            const title = _("Unauthorized Thunderbolt device");
	    const body = _("New device has been detected and needs to be authorized by an administrator.");
	    this._notify(title, body);
        }
    }

    _onEnrollFailed(obj, device, error) {
	const title = _("Thunderbolt authorization error");
	const body = _("Could not authorize the Thunderbolt device: %s").format(error.message);
	this._notify(title, body);
    }
};
(uuay)extensionDownloader.jsA$// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Soup, St } = imports.gi;

const Config = imports.misc.config;
const ExtensionUtils = imports.misc.extensionUtils;
const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;

var REPOSITORY_URL_BASE = 'https://extensions.gnome.org';
var REPOSITORY_URL_DOWNLOAD = REPOSITORY_URL_BASE + '/download-extension/%s.shell-extension.zip';
var REPOSITORY_URL_INFO     = REPOSITORY_URL_BASE + '/extension-info/';
var REPOSITORY_URL_UPDATE   = REPOSITORY_URL_BASE + '/update-info/';

let _httpSession;

function installExtension(uuid, invocation) {
    const oldExt = Main.extensionManager.lookup(uuid);
    if (oldExt && oldExt.type === ExtensionUtils.ExtensionType.SYSTEM) {
        log('extensionDownloader: Trying to replace system extension %s'.format(uuid));
        invocation.return_dbus_error('org.gnome.Shell.InstallError',
            'System extensions cannot be replaced');
        return;
    }

    let params = { uuid: uuid,
                   shell_version: Config.PACKAGE_VERSION };

    let message = Soup.form_request_new_from_hash('GET', REPOSITORY_URL_INFO, params);

    _httpSession.queue_message(message, (session, message) => {
        if (message.status_code != Soup.KnownStatusCode.OK) {
            Main.extensionManager.logExtensionError(uuid, 'downloading info: ' + message.status_code);
            invocation.return_dbus_error('org.gnome.Shell.DownloadInfoError', message.status_code.toString());
            return;
        }

        let info;
        try {
            info = JSON.parse(message.response_body.data);
        } catch (e) {
            Main.extensionManager.logExtensionError(uuid, 'parsing info: ' + e);
            invocation.return_dbus_error('org.gnome.Shell.ParseInfoError', e.toString());
            return;
        }

        let dialog = new InstallExtensionDialog(uuid, info, invocation);
        dialog.open(global.get_current_time());
    });
}

function uninstallExtension(uuid) {
    let extension = Main.extensionManager.lookup(uuid);
    if (!extension)
        return false;

    // Don't try to uninstall system extensions
    if (extension.type != ExtensionUtils.ExtensionType.PER_USER)
        return false;

    if (!Main.extensionManager.unloadExtension(extension))
        return false;

    FileUtils.recursivelyDeleteDir(extension.dir, true);

    try {
        const updatesDir = Gio.File.new_for_path(GLib.build_filenamev(
            [global.userdatadir, 'extension-updates', extension.uuid]));
        FileUtils.recursivelyDeleteDir(updatesDir, true);
    } catch (e) {
        // not an error
    }

    return true;
}

function gotExtensionZipFile(session, message, uuid, dir, callback, errback) {
    if (message.status_code != Soup.KnownStatusCode.OK) {
        errback('DownloadExtensionError', message.status_code);
        return;
    }

    try {
        if (!dir.query_exists(null))
            dir.make_directory_with_parents(null);
    } catch (e) {
        errback('CreateExtensionDirectoryError', e);
        return;
    }

    let [file, stream] = Gio.File.new_tmp('XXXXXX.shell-extension.zip');
    let contents = message.response_body.flatten().get_as_bytes();
    stream.output_stream.write_bytes(contents, null);
    stream.close(null);
    let [success, pid] = GLib.spawn_async(null,
                                          ['unzip', '-uod', dir.get_path(), '--', file.get_path()],
                                          null,
                                          GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                                          null);

    if (!success) {
        errback('ExtractExtensionError');
        return;
    }

    GLib.child_watch_add(GLib.PRIORITY_DEFAULT, pid, (pid, status) => {
        GLib.spawn_close_pid(pid);

        if (status != 0)
            errback('ExtractExtensionError');
        else
            callback();
    });
}

function downloadExtensionUpdate(uuid) {
    let dir = Gio.File.new_for_path(
        GLib.build_filenamev([global.userdatadir, 'extension-updates', uuid]));

    let params = { shell_version: Config.PACKAGE_VERSION };

    let url = REPOSITORY_URL_DOWNLOAD.format(uuid);
    let message = Soup.form_request_new_from_hash('GET', url, params);

    _httpSession.queue_message(message, session => {
        gotExtensionZipFile(session, message, uuid, dir, () => {
            Main.extensionManager.notifyExtensionUpdate(uuid);
        }, (code, msg) => {
            log(`Error while downloading update for extension ${uuid}: ${code} (${msg})`);
        });
    });
}

function checkForUpdates() {
    let metadatas = {};
    Main.extensionManager.getUuids().forEach(uuid => {
        let extension = Main.extensionManager.lookup(uuid);
        if (extension.type !== ExtensionUtils.ExtensionType.PER_USER)
            return;
        if (extension.hasUpdate)
            return;
        metadatas[uuid] = extension.metadata;
    });

    let versionCheck = global.settings.get_boolean(
        'disable-extension-version-validation');
    let params = {
        shell_version: Config.PACKAGE_VERSION,
        installed: JSON.stringify(metadatas),
        disable_version_validation: `${versionCheck}`,
    };

    let url = REPOSITORY_URL_UPDATE;
    let message = Soup.form_request_new_from_hash('GET', url, params);
    _httpSession.queue_message(message, (session, message) => {
        if (message.status_code != Soup.KnownStatusCode.OK)
            return;

        let operations = JSON.parse(message.response_body.data);
        for (let uuid in operations) {
            let operation = operations[uuid];
            if (operation == 'blacklist')
                uninstallExtension(uuid);
            else if (operation == 'upgrade' || operation == 'downgrade')
                downloadExtensionUpdate(uuid);
        }
    });
}

var InstallExtensionDialog =
class InstallExtensionDialog extends ModalDialog.ModalDialog {
    constructor(uuid, info, invocation) {
        super({ styleClass: 'extension-dialog' });

        this._uuid = uuid;
        this._info = info;
        this._invocation = invocation;

        this.setButtons([{ label: _("Cancel"),
                           action: this._onCancelButtonPressed.bind(this),
                           key:    Clutter.Escape
                         },
                         { label:  _("Install"),
                           action: this._onInstallButtonPressed.bind(this),
                           default: true
                         }]);

        let message = _("Download and install “%s” from extensions.gnome.org?").format(info.name);

        let box = new St.BoxLayout({ style_class: 'message-dialog-main-layout',
                                     vertical: false });
        this.contentLayout.add(box);

        let gicon = new Gio.FileIcon({ file: Gio.File.new_for_uri(REPOSITORY_URL_BASE + info.icon) })
        let icon = new St.Icon({ gicon: gicon });
        box.add(icon);

        let label = new St.Label({ style_class: 'message-dialog-title headline',
                                   text: message });
        box.add(label);
    }

    _onCancelButtonPressed(button, event) {
        this.close();
        this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
    }

    _onInstallButtonPressed(button, event) {
        let params = { shell_version: Config.PACKAGE_VERSION };

        let url = REPOSITORY_URL_DOWNLOAD.format(this._uuid);
        let message = Soup.form_request_new_from_hash('GET', url, params);

        let uuid = this._uuid;
        let dir = Gio.File.new_for_path(GLib.build_filenamev([global.userdatadir, 'extensions', uuid]));
        let invocation = this._invocation;
        function errback(code, message) {
            let msg = message ? message.toString() : '';
            log('Error while installing %s: %s (%s)'.format(uuid, code, msg));
            invocation.return_dbus_error('org.gnome.Shell.' + code, msg);
        }

        function callback() {
            try {
                let extension = Main.extensionManager.createExtensionObject(uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
                Main.extensionManager.loadExtension(extension);
                if (!Main.extensionManager.enableExtension(uuid))
                    throw new Error(`Cannot add ${uuid} to enabled extensions gsettings key`);
            } catch(e) {
                uninstallExtension(uuid);
                errback('LoadExtensionError', e);
                return;
            }

            invocation.return_value(GLib.Variant.new('(s)', ['successful']));
        }

        _httpSession.queue_message(message, (session, message) => {
            gotExtensionZipFile(session, message, uuid, dir, callback, errback);
        });

        this.close();
    }
};

function init() {
    _httpSession = new Soup.SessionAsync({ ssl_use_system_ca_file: true });

    // See: https://bugzilla.gnome.org/show_bug.cgi?id=655189 for context.
    // _httpSession.add_feature(new Soup.ProxyResolverDefault());
    Soup.Session.prototype.add_feature.call(_httpSession, new Soup.ProxyResolverDefault());
}
(uuay)ibusManager.jsM // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Gio, GLib, IBus } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const IBusCandidatePopup = imports.ui.ibusCandidatePopup;

// Ensure runtime version matches
_checkIBusVersion(1, 5, 2);

let _ibusManager = null;

function _checkIBusVersion(requiredMajor, requiredMinor, requiredMicro) {
    if ((IBus.MAJOR_VERSION > requiredMajor) ||
        (IBus.MAJOR_VERSION == requiredMajor && IBus.MINOR_VERSION > requiredMinor) ||
        (IBus.MAJOR_VERSION == requiredMajor && IBus.MINOR_VERSION == requiredMinor &&
         IBus.MICRO_VERSION >= requiredMicro))
        return;

    throw "Found IBus version %d.%d.%d but required is %d.%d.%d".
        format(IBus.MAJOR_VERSION, IBus.MINOR_VERSION, IBus.MINOR_VERSION,
               requiredMajor, requiredMinor, requiredMicro);
}

function getIBusManager() {
    if (_ibusManager == null)
        _ibusManager = new IBusManager();
    return _ibusManager;
}

var IBusManager = class {
    constructor() {
        IBus.init();

        // This is the longest we'll keep the keyboard frozen until an input
        // source is active.
        this._MAX_INPUT_SOURCE_ACTIVATION_TIME = 4000; // ms
        this._PRELOAD_ENGINES_DELAY_TIME = 30; // sec


        this._candidatePopup = new IBusCandidatePopup.CandidatePopup();

        this._panelService = null;
        this._engines = {};
        this._ready = false;
        this._registerPropertiesId = 0;
        this._currentEngineName = null;
        this._preloadEnginesId = 0;

        this._ibus = IBus.Bus.new_async();
        this._ibus.connect('connected', this._onConnected.bind(this));
        this._ibus.connect('disconnected', this._clear.bind(this));
        // Need to set this to get 'global-engine-changed' emitions
        this._ibus.set_watch_ibus_signal(true);
        this._ibus.connect('global-engine-changed', this._engineChanged.bind(this));

        this._spawn();
    }

    _spawn() {
        try {
            Gio.Subprocess.new(['ibus-daemon', '--xim', '--panel', 'disable'],
                               Gio.SubprocessFlags.NONE);
        } catch(e) {
            log('Failed to launch ibus-daemon: ' + e.message);
        }
    }

    _clear() {
        if (this._panelService)
            this._panelService.destroy();

        this._panelService = null;
        this._candidatePopup.setPanelService(null);
        this._engines = {};
        this._ready = false;
        this._registerPropertiesId = 0;
        this._currentEngineName = null;

        this.emit('ready', false);

        this._spawn();
    }

    _onConnected() {
        this._ibus.list_engines_async(-1, null, this._initEngines.bind(this));
        this._ibus.request_name_async(IBus.SERVICE_PANEL,
                                      IBus.BusNameFlag.REPLACE_EXISTING,
                                      -1, null,
                                      this._initPanelService.bind(this));
    }

    _initEngines(ibus, result) {
        let enginesList = this._ibus.list_engines_async_finish(result);
        if (enginesList) {
            for (let i = 0; i < enginesList.length; ++i) {
                let name = enginesList[i].get_name();
                this._engines[name] = enginesList[i];
            }
            this._updateReadiness();
        } else {
            this._clear();
        }
    }

    _initPanelService(ibus, result) {
        let success = this._ibus.request_name_async_finish(result);
        if (success) {
            this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
                                                         object_path: IBus.PATH_PANEL });
            this._candidatePopup.setPanelService(this._panelService);
            this._panelService.connect('update-property', this._updateProperty.bind(this));
            this._panelService.connect('set-cursor-location', (ps, x, y, w, h) => {
                let cursorLocation = { x, y, width: w, height: h };
                this.emit('set-cursor-location', cursorLocation);
            });
            this._panelService.connect('focus-in', (panel, path) => {
                if (!GLib.str_has_suffix(path, '/InputContext_1'))
                    this.emit ('focus-in');
            });
            this._panelService.connect('focus-out', () => { this.emit('focus-out'); });

            try {
                // IBus versions older than 1.5.10 have a bug which
                // causes spurious set-content-type emissions when
                // switching input focus that temporarily lose purpose
                // and hints defeating its intended semantics and
                // confusing users. We thus don't use it in that case.
                _checkIBusVersion(1, 5, 10);
                this._panelService.connect('set-content-type', this._setContentType.bind(this));
            } catch (e) {
            }
            // If an engine is already active we need to get its properties
            this._ibus.get_global_engine_async(-1, null, (i, result) => {
                let engine;
                try {
                    engine = this._ibus.get_global_engine_async_finish(result);
                    if (!engine)
                        return;
                } catch(e) {
                    return;
                }
                this._engineChanged(this._ibus, engine.get_name());
            });
            this._updateReadiness();
        } else {
            this._clear();
        }
    }

    _updateReadiness() {
        this._ready = (Object.keys(this._engines).length > 0 &&
                       this._panelService != null);
        this.emit('ready', this._ready);
    }

    _engineChanged(bus, engineName) {
        if (!this._ready)
            return;

        this._currentEngineName = engineName;

        if (this._registerPropertiesId != 0)
            return;

        this._registerPropertiesId =
            this._panelService.connect('register-properties', (p, props) => {
                if (!props.get(0))
                    return;

                this._panelService.disconnect(this._registerPropertiesId);
                this._registerPropertiesId = 0;

                this.emit('properties-registered', this._currentEngineName, props);
            });
    }

    _updateProperty(panel, prop) {
        this.emit('property-updated', this._currentEngineName, prop);
    }

    _setContentType(panel, purpose, hints) {
        this.emit('set-content-type', purpose, hints);
    }

    activateProperty(key, state) {
        this._panelService.property_activate(key, state);
    }

    getEngineDesc(id) {
        if (!this._ready || !this._engines.hasOwnProperty(id))
            return null;

        return this._engines[id];
    }

    setEngine(id, callback) {
        // Send id even if id == this._currentEngineName
        // because 'properties-registered' signal can be emitted
        // while this._ibusSources == null on a lock screen.
        if (!this._ready) {
            if (callback)
                callback();
            return;
        }

        this._ibus.set_global_engine_async(id, this._MAX_INPUT_SOURCE_ACTIVATION_TIME,
                                           null, callback || null);
    }

    preloadEngines(ids) {
        if (!this._ibus || ids.length == 0)
            return;

        if (this._preloadEnginesId != 0) {
            Mainloop.source_remove(this._preloadEnginesId);
            this._preloadEnginesId = 0;
        }

        this._preloadEnginesId =
            Mainloop.timeout_add_seconds(this._PRELOAD_ENGINES_DELAY_TIME,
                                         () => {
                                             this._ibus.preload_engines_async(
                                                 ids,
                                                 -1,
                                                 null,
                                                 null);
                                             this._preloadEnginesId = 0;
                                             return GLib.SOURCE_REMOVE;
                                         });
    }
};
Signals.addSignalMethods(IBusManager.prototype);
(uuay)appFavorites.jsI// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const Shell = imports.gi.Shell;
const Signals = imports.signals;

const Main = imports.ui.main;

// In alphabetical order
const RENAMED_DESKTOP_IDS = {
    'baobab.desktop': 'org.gnome.baobab.desktop',
    'cheese.desktop': 'org.gnome.Cheese.desktop',
    'dconf-editor.desktop': 'ca.desrt.dconf-editor.desktop',
    'empathy.desktop': 'org.gnome.Empathy.desktop',
    'epiphany.desktop': 'org.gnome.Epiphany.desktop',
    'evolution.desktop': 'org.gnome.Evolution.desktop',
    'file-roller.desktop': 'org.gnome.FileRoller.desktop',
    'five-or-more.desktop': 'org.gnome.five-or-more.desktop',
    'four-in-a-row.desktop': 'org.gnome.Four-in-a-row.desktop',
    'gcalctool.desktop': 'org.gnome.Calculator.desktop',
    'geary.desktop': 'org.gnome.Geary.desktop',
    'gedit.desktop': 'org.gnome.gedit.desktop',
    'glchess.desktop': 'org.gnome.Chess.desktop',
    'glines.desktop': 'org.gnome.five-or-more.desktop',
    'gnect.desktop': 'org.gnome.Four-in-a-row.desktop',
    'gnibbles.desktop': 'org.gnome.Nibbles.desktop',
    'gnobots2.desktop': 'org.gnome.Robots.desktop',
    'gnome-boxes.desktop': 'org.gnome.Boxes.desktop',
    'gnome-calculator.desktop': 'org.gnome.Calculator.desktop',
    'gnome-chess.desktop': 'org.gnome.Chess.desktop',
    'gnome-clocks.desktop': 'org.gnome.clocks.desktop',
    'gnome-contacts.desktop': 'org.gnome.Contacts.desktop',
    'gnome-documents.desktop': 'org.gnome.Documents.desktop',
    'gnome-font-viewer.desktop': 'org.gnome.font-viewer.desktop',
    'gnome-klotski.desktop': 'org.gnome.Klotski.desktop',
    'gnome-nibbles.desktop': 'org.gnome.Nibbles.desktop',
    'gnome-mahjongg.desktop': 'org.gnome.Mahjongg.desktop',
    'gnome-mines.desktop': 'org.gnome.Mines.desktop',
    'gnome-music.desktop': 'org.gnome.Music.desktop',
    'gnome-photos.desktop': 'org.gnome.Photos.desktop',
    'gnome-robots.desktop': 'org.gnome.Robots.desktop',
    'gnome-screenshot.desktop': 'org.gnome.Screenshot.desktop',
    'gnome-software.desktop': 'org.gnome.Software.desktop',
    'gnome-terminal.desktop': 'org.gnome.Terminal.desktop',
    'gnome-tetravex.desktop': 'org.gnome.Tetravex.desktop',
    'gnome-tweaks.desktop': 'org.gnome.tweaks.desktop',
    'gnome-weather.desktop': 'org.gnome.Weather.desktop',
    'gnomine.desktop': 'org.gnome.Mines.desktop',
    'gnotravex.desktop': 'org.gnome.Tetravex.desktop',
    'gnotski.desktop': 'org.gnome.Klotski.desktop',
    'gtali.desktop': 'org.gnome.Tali.desktop',
    'iagno.desktop': 'org.gnome.Reversi.desktop',
    'mozilla-firefox.desktop': 'firefox.desktop',
    'nautilus.desktop': 'org.gnome.Nautilus.desktop',
    'org.gnome.gnome-2048.desktop': 'org.gnome.TwentyFortyEight.desktop',
    'org.gnome.taquin.desktop': 'org.gnome.Taquin.desktop',
    'org.gnome.Weather.Application.desktop': 'org.gnome.Weather.desktop',
    'polari.desktop': 'org.gnome.Polari.desktop',
    'tali.desktop': 'org.gnome.Tali.desktop',
    'totem.desktop': 'org.gnome.Totem.desktop',
    'evince.desktop': 'org.gnome.Evince.desktop',
};

class AppFavorites {
    constructor() {
        this.FAVORITE_APPS_KEY = 'favorite-apps';
        this._favorites = {};
        global.settings.connect('changed::' + this.FAVORITE_APPS_KEY, this._onFavsChanged.bind(this));
        this.reload();
    }

    _onFavsChanged() {
        this.reload();
        this.emit('changed');
    }

    reload() {
        let ids = global.settings.get_strv(this.FAVORITE_APPS_KEY);
        let appSys = Shell.AppSystem.get_default();

        // Map old desktop file names to the current ones
        let updated = false;
        ids = ids.map(id => {
            let newId = RENAMED_DESKTOP_IDS[id];
            if (newId !== undefined &&
                appSys.lookup_app(newId) != null) {
                updated = true;
                return newId;
            }
            return id;
        });
        // ... and write back the updated desktop file names
        if (updated)
            global.settings.set_strv(this.FAVORITE_APPS_KEY, ids);

        let apps = ids.map(id => appSys.lookup_app(id))
                      .filter(app => app != null);
        this._favorites = {};
        for (let i = 0; i < apps.length; i++) {
            let app = apps[i];
            this._favorites[app.get_id()] = app;
        }
    }

    _getIds() {
        let ret = [];
        for (let id in this._favorites)
            ret.push(id);
        return ret;
    }

    getFavoriteMap() {
        return this._favorites;
    }

    getFavorites() {
        let ret = [];
        for (let id in this._favorites)
            ret.push(this._favorites[id]);
        return ret;
    }

    isFavorite(appId) {
        return appId in this._favorites;
    }

    _addFavorite(appId, pos) {
        if (appId in this._favorites)
            return false;

        let app = Shell.AppSystem.get_default().lookup_app(appId);

        if (!app)
            return false;

        let ids = this._getIds();
        if (pos == -1)
            ids.push(appId);
        else
            ids.splice(pos, 0, appId);
        global.settings.set_strv(this.FAVORITE_APPS_KEY, ids);
        return true;
    }

    addFavoriteAtPos(appId, pos) {
        if (!this._addFavorite(appId, pos))
            return;

        let app = Shell.AppSystem.get_default().lookup_app(appId);

        Main.overview.setMessage(_("%s has been added to your favorites.").format(app.get_name()),
                                 { forFeedback: true,
                                   undoCallback: () => {
                                       this._removeFavorite(appId);
                                   }
                                 });
    }

    addFavorite(appId) {
        this.addFavoriteAtPos(appId, -1);
    }

    moveFavoriteToPos(appId, pos) {
        this._removeFavorite(appId);
        this._addFavorite(appId, pos);
    }

    _removeFavorite(appId) {
        if (!(appId in this._favorites))
            return false;

        let ids = this._getIds().filter(id => id != appId);
        global.settings.set_strv(this.FAVORITE_APPS_KEY, ids);
        return true;
    }

    removeFavorite(appId) {
        let ids = this._getIds();
        let pos = ids.indexOf(appId);

        let app = this._favorites[appId];
        if (!this._removeFavorite(appId))
            return;

        Main.overview.setMessage(_("%s has been removed from your favorites.").format(app.get_name()),
                                 { forFeedback: true,
                                   undoCallback: () => {
                                       this._addFavorite(appId, pos);
                                   }
                                 });
    }
};
Signals.addSignalMethods(AppFavorites.prototype);

var appFavoritesInstance = null;
function getAppFavorites() {
    if (appFavoritesInstance == null)
        appFavoritesInstance = new AppFavorites();
    return appFavoritesInstance;
}
(uuay)messageTray.jsj�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Mainloop = imports.mainloop;
const Signals = imports.signals;

const Calendar = imports.ui.calendar;
const GnomeSession = imports.misc.gnomeSession;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Tweener = imports.ui.tweener;

const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';

var ANIMATION_TIME = 0.2;
var NOTIFICATION_TIMEOUT = 4;

var HIDE_TIMEOUT = 0.2;
var LONGER_HIDE_TIMEOUT = 0.6;

var MAX_NOTIFICATIONS_IN_QUEUE = 3;
var MAX_NOTIFICATIONS_PER_SOURCE = 3;
var MAX_NOTIFICATION_BUTTONS = 3;

// We delay hiding of the tray if the mouse is within MOUSE_LEFT_ACTOR_THRESHOLD
// range from the point where it left the tray.
var MOUSE_LEFT_ACTOR_THRESHOLD = 20;

var IDLE_TIME = 1000;

var State = {
    HIDDEN:  0,
    SHOWING: 1,
    SHOWN:   2,
    HIDING:  3
};

// These reasons are useful when we destroy the notifications received through
// the notification daemon. We use EXPIRED for notifications that we dismiss
// and the user did not interact with, DISMISSED for all other notifications
// that were destroyed as a result of a user action, SOURCE_CLOSED for the
// notifications that were requested to be destroyed by the associated source,
// and REPLACED for notifications that were destroyed as a consequence of a
// newer version having replaced them.
var NotificationDestroyedReason = {
    EXPIRED: 1,
    DISMISSED: 2,
    SOURCE_CLOSED: 3,
    REPLACED: 4
};

// Message tray has its custom Urgency enumeration. LOW, NORMAL and CRITICAL
// urgency values map to the corresponding values for the notifications received
// through the notification daemon. HIGH urgency value is used for chats received
// through the Telepathy client.
var Urgency = {
    LOW: 0,
    NORMAL: 1,
    HIGH: 2,
    CRITICAL: 3
};

// The privacy of the details of a notification. USER is for notifications which
// contain private information to the originating user account (for example,
// details of an e-mail they’ve received). SYSTEM is for notifications which
// contain information private to the physical system (for example, battery
// status) and hence the same for every user. This affects whether the content
// of a notification is shown on the lock screen.
var PrivacyScope = {
    USER: 0,
    SYSTEM: 1,
};

var FocusGrabber = class FocusGrabber {
    constructor(actor) {
        this._actor = actor;
        this._prevKeyFocusActor = null;
        this._focusActorChangedId = 0;
        this._focused = false;
    }

    grabFocus() {
        if (this._focused)
            return;

        this._prevKeyFocusActor = global.stage.get_key_focus();

        this._focusActorChangedId = global.stage.connect('notify::key-focus', this._focusActorChanged.bind(this));

        if (!this._actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false))
            this._actor.grab_key_focus();

        this._focused = true;
    }

    _focusUngrabbed() {
        if (!this._focused)
            return false;

        if (this._focusActorChangedId > 0) {
            global.stage.disconnect(this._focusActorChangedId);
            this._focusActorChangedId = 0;
        }

        this._focused = false;
        return true;
    }

    _focusActorChanged() {
        let focusedActor = global.stage.get_key_focus();
        if (!focusedActor || !this._actor.contains(focusedActor))
            this._focusUngrabbed();
    }

    ungrabFocus() {
        if (!this._focusUngrabbed())
            return;

        if (this._prevKeyFocusActor) {
            global.stage.set_key_focus(this._prevKeyFocusActor);
            this._prevKeyFocusActor = null;
        } else {
            let focusedActor = global.stage.get_key_focus();
            if (focusedActor && this._actor.contains(focusedActor))
                global.stage.set_key_focus(null);
        }
    }
};

// NotificationPolicy:
// An object that holds all bits of configurable policy related to a notification
// source, such as whether to play sound or honour the critical bit.
//
// A notification without a policy object will inherit the default one.
var NotificationPolicy = class NotificationPolicy {
    constructor(params) {
        params = Params.parse(params, { enable: true,
                                        enableSound: true,
                                        showBanners: true,
                                        forceExpanded: false,
                                        showInLockScreen: true,
                                        detailsInLockScreen: false
                                      });
        Object.getOwnPropertyNames(params).forEach(key => {
            let desc = Object.getOwnPropertyDescriptor(params, key);
            Object.defineProperty(this, `_${key}`, desc);
        });
    }

    // Do nothing for the default policy. These methods are only useful for the
    // GSettings policy.
    store() { }
    destroy() { }

    get enable() {
        return this._enable;
    }

    get enableSound() {
        return this._enableSound;
    }

    get showBanners() {
        return this._showBanners;
    }

    get forceExpanded() {
        return this._forceExpanded;
    }

    get showInLockScreen() {
        return this._showInLockScreen;
    }

    get detailsInLockScreen() {
        return this._detailsInLockScreen;
    }
};
Signals.addSignalMethods(NotificationPolicy.prototype);

var NotificationGenericPolicy =
class NotificationGenericPolicy extends NotificationPolicy {
    constructor() {
        super();
        this.id = 'generic';

        this._masterSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications' });
        this._masterSettings.connect('changed', this._changed.bind(this));
    }

    store() { }

    destroy() {
        this._masterSettings.run_dispose();
    }

    _changed(settings, key) {
        this.emit('policy-changed', key);
    }

    get showBanners() {
        return this._masterSettings.get_boolean('show-banners');
    }

    get showInLockScreen() {
        return this._masterSettings.get_boolean('show-in-lock-screen');
    }
};

var NotificationApplicationPolicy =
class NotificationApplicationPolicy extends NotificationPolicy {
    constructor(id) {
        super();

        this.id = id;
        this._canonicalId = this._canonicalizeId(id);

        this._masterSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications' });
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications.application',
                                            path: '/org/gnome/desktop/notifications/application/' + this._canonicalId + '/' });

        this._masterSettings.connect('changed', this._changed.bind(this));
        this._settings.connect('changed', this._changed.bind(this));
    }

    store() {
        this._settings.set_string('application-id', this.id + '.desktop');

        let apps = this._masterSettings.get_strv('application-children');
        if (apps.indexOf(this._canonicalId) < 0) {
            apps.push(this._canonicalId);
            this._masterSettings.set_strv('application-children', apps);
        }
    }

    destroy() {
        this._masterSettings.run_dispose();
        this._settings.run_dispose();
    }

    _changed(settings, key) {
        this.emit('policy-changed', key);
        if (key == 'enable')
            this.emit('enable-changed');
    }

    _canonicalizeId(id) {
        // Keys are restricted to lowercase alphanumeric characters and dash,
        // and two dashes cannot be in succession
        return id.toLowerCase().replace(/[^a-z0-9\-]/g, '-').replace(/--+/g, '-');
    }

    get enable() {
        return this._settings.get_boolean('enable');
    }

    get enableSound() {
        return this._settings.get_boolean('enable-sound-alerts');
    }

    get showBanners() {
        return this._masterSettings.get_boolean('show-banners') &&
            this._settings.get_boolean('show-banners');
    }

    get forceExpanded() {
        return this._settings.get_boolean('force-expanded');
    }

    get showInLockScreen() {
        return this._masterSettings.get_boolean('show-in-lock-screen') &&
            this._settings.get_boolean('show-in-lock-screen');
    }

    get detailsInLockScreen() {
        return this._settings.get_boolean('details-in-lock-screen');
    }
};

// Notification:
// @source: the notification's Source
// @title: the title
// @banner: the banner text
// @params: optional additional params
//
// Creates a notification. In the banner mode, the notification
// will show an icon, @title (in bold) and @banner, all on a single
// line (with @banner ellipsized if necessary).
//
// The notification will be expandable if either it has additional
// elements that were added to it or if the @banner text did not
// fit fully in the banner mode. When the notification is expanded,
// the @banner text from the top line is always removed. The complete
// @banner text is added as the first element in the content section,
// unless 'customContent' parameter with the value 'true' is specified
// in @params.
//
// Additional notification content can be added with addActor() and
// addBody() methods. The notification content is put inside a
// scrollview, so if it gets too tall, the notification will scroll
// rather than continue to grow. In addition to this main content
// area, there is also a single-row action area, which is not
// scrolled and can contain a single actor. The action area can
// be set by calling setActionArea() method. There is also a
// convenience method addButton() for adding a button to the action
// area.
//
// If @params contains a 'customContent' parameter with the value %true,
// then @banner will not be shown in the body of the notification when the
// notification is expanded and calls to update() will not clear the content
// unless 'clear' parameter with value %true is explicitly specified.
//
// By default, the icon shown is the same as the source's.
// However, if @params contains a 'gicon' parameter, the passed in gicon
// will be used.
//
// You can add a secondary icon to the banner with 'secondaryGIcon'. There
// is no fallback for this icon.
//
// If @params contains 'bannerMarkup', with the value %true, a subset (<b>,
// <i> and <u>) of the markup in [1] will be interpreted within @banner. If
// the parameter is not present, then anything that looks like markup
// in @banner will appear literally in the output.
//
// If @params contains a 'clear' parameter with the value %true, then
// the content and the action area of the notification will be cleared.
// The content area is also always cleared if 'customContent' is false
// because it might contain the @banner that didn't fit in the banner mode.
//
// If @params contains 'soundName' or 'soundFile', the corresponding
// event sound is played when the notification is shown (if the policy for
// @source allows playing sounds).
//
// [1] https://developer.gnome.org/notification-spec/#markup 
var Notification = class Notification {
    constructor(source, title, banner, params) {
        this.source = source;
        this.title = title;
        this.urgency = Urgency.NORMAL;
        this.resident = false;
        // 'transient' is a reserved keyword in JS, so we have to use an alternate variable name
        this.isTransient = false;
        this.privacyScope = PrivacyScope.USER;
        this.forFeedback = false;
        this._acknowledged = false;
        this.bannerBodyText = null;
        this.bannerBodyMarkup = false;
        this._soundName = null;
        this._soundFile = null;
        this._soundPlayed = false;
        this.actions = [];

        // If called with only one argument we assume the caller
        // will call .update() later on. This is the case of
        // NotificationDaemon, which wants to use the same code
        // for new and updated notifications
        if (arguments.length != 1)
            this.update(title, banner, params);
    }

    // update:
    // @title: the new title
    // @banner: the new banner
    // @params: as in the Notification constructor
    //
    // Updates the notification by regenerating its icon and updating
    // the title/banner. If @params.clear is %true, it will also
    // remove any additional actors/action buttons previously added.
    update(title, banner, params) {
        params = Params.parse(params, { gicon: null,
                                        secondaryGIcon: null,
                                        bannerMarkup: false,
                                        clear: false,
                                        datetime: null,
                                        soundName: null,
                                        soundFile: null });

        this.title = title;
        this.bannerBodyText = banner;
        this.bannerBodyMarkup = params.bannerMarkup;

        if (params.datetime)
            this.datetime = params.datetime;
        else
            this.datetime = GLib.DateTime.new_now_local();

        if (params.gicon || params.clear)
            this.gicon = params.gicon;

        if (params.secondaryGIcon || params.clear)
            this.secondaryGIcon = params.secondaryGIcon;

        if (params.clear)
            this.actions = [];

        if (this._soundName != params.soundName ||
            this._soundFile != params.soundFile) {
            this._soundName = params.soundName;
            this._soundFile = params.soundFile;
            this._soundPlayed = false;
        }

        this.emit('updated', params.clear);
    }

    // addAction:
    // @label: the label for the action's button
    // @callback: the callback for the action
    addAction(label, callback) {
        this.actions.push({ label: label, callback: callback });
    }

    get acknowledged() {
        return this._acknowledged;
    }

    set acknowledged(v) {
        if (this._acknowledged == v)
            return;
        this._acknowledged = v;
        this.emit('acknowledged-changed');
    }

    setUrgency(urgency) {
        this.urgency = urgency;
    }

    setResident(resident) {
        this.resident = resident;
    }

    setTransient(isTransient) {
        this.isTransient = isTransient;
    }

    setForFeedback(forFeedback) {
        this.forFeedback = forFeedback;
    }

    setPrivacyScope(privacyScope) {
        this.privacyScope = privacyScope;
    }

    playSound() {
        if (this._soundPlayed)
            return;

        if (!this.source.policy.enableSound) {
            this._soundPlayed = true;
            return;
        }

        let player = global.display.get_sound_player();
        if (this._soundName)
            player.play_from_theme(this._soundName, this.title, null);
        else if (this._soundFile)
            player.play_from_file(this._soundFile, this.title, null);
    }

    // Allow customizing the banner UI:
    // the default implementation defers the creation to
    // the source (which will create a NotificationBanner),
    // so customization can be done by subclassing either
    // Notification or Source
    createBanner() {
        return this.source.createBanner(this);
    }

    activate() {
        this.emit('activated');
        if (!this.resident)
            this.destroy();
    }

    destroy(reason) {
        if (!reason)
            reason = NotificationDestroyedReason.DISMISSED;
        this.emit('destroy', reason);
    }
};
Signals.addSignalMethods(Notification.prototype);

var NotificationBanner =
class NotificationBanner extends Calendar.NotificationMessage {
    constructor(notification) {
        super(notification);

        this.actor.can_focus = false;
        this.actor.add_style_class_name('notification-banner');

        this._buttonBox = null;

        this._addActions();
        this._addSecondaryIcon();

        this._activatedId = this.notification.connect('activated', () => {
            // We hide all types of notifications once the user clicks on
            // them because the common outcome of clicking should be the
            // relevant window being brought forward and the user's
            // attention switching to the window.
            this.emit('done-displaying');
        });
    }

    _disconnectNotificationSignals() {
        super._disconnectNotificationSignals();

        if (this._activatedId)
            this.notification.disconnect(this._activatedId);
        this._activatedId = 0;
    }

    _onUpdated(n, clear) {
        super._onUpdated(n, clear);

        if (clear) {
            this.setSecondaryActor(null);
            this.setActionArea(null);
            this._buttonBox = null;
        }

        this._addActions();
        this._addSecondaryIcon();
    }

    _addActions() {
        this.notification.actions.forEach(action => {
            this.addAction(action.label, action.callback);
        });
    }

    _addSecondaryIcon() {
        if (this.notification.secondaryGIcon) {
            let icon = new St.Icon({ gicon: this.notification.secondaryGIcon,
                                     x_align: Clutter.ActorAlign.END });
            this.setSecondaryActor(icon);
        }
    }

    addButton(button, callback) {
        if (!this._buttonBox) {
            this._buttonBox = new St.BoxLayout({ style_class: 'notification-actions',
                                                 x_expand: true });
            this.setActionArea(this._buttonBox);
            global.focus_manager.add_group(this._buttonBox);
        }

        if (this._buttonBox.get_n_children() >= MAX_NOTIFICATION_BUTTONS)
            return null;

        this._buttonBox.add(button);
        button.connect('clicked', () => {
            callback();

            if (!this.notification.resident) {
                // We don't hide a resident notification when the user invokes one of its actions,
                // because it is common for such notifications to update themselves with new
                // information based on the action. We'd like to display the updated information
                // in place, rather than pop-up a new notification.
                this.emit('done-displaying');
                this.notification.destroy();
            }
        });

        return button;
    }

    addAction(label, callback) {
        let button = new St.Button({ style_class: 'notification-button',
                                     label: label,
                                     x_expand: true,
                                     can_focus: true });

        return this.addButton(button, callback);
    }
};

var SourceActor = GObject.registerClass(
class SourceActor extends St.Widget {
    _init(source, size) {
        super._init();

        this._source = source;
        this._size = size;

        this.actor = this;
        this.connect('destroy', () => {
            this._source.disconnect(this._iconUpdatedId);
            this._actorDestroyed = true;
        });
        this._actorDestroyed = false;

        let scale_factor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        this._iconBin = new St.Bin({ x_fill: true,
                                     x_expand: true,
                                     height: size * scale_factor,
                                     width: size * scale_factor });

        this.add_actor(this._iconBin);

        this._iconUpdatedId = this._source.connect('icon-updated', this._updateIcon.bind(this));
        this._updateIcon();
    }

    setIcon(icon) {
        this._iconBin.child = icon;
        this._iconSet = true;
    }

    _updateIcon() {
        if (this._actorDestroyed)
            return;

        if (!this._iconSet)
            this._iconBin.child = this._source.createIcon(this._size);
    }
});

var SourceActorWithLabel = GObject.registerClass(
class SourceActorWithLabel extends SourceActor {
    _init(source, size) {
        super._init(source, size);

        this._counterLabel = new St.Label({ x_align: Clutter.ActorAlign.CENTER,
                                            x_expand: true,
                                            y_align: Clutter.ActorAlign.CENTER,
                                            y_expand: true });

        this._counterBin = new St.Bin({ style_class: 'summary-source-counter',
                                        child: this._counterLabel,
                                        layout_manager: new Clutter.BinLayout() });
        this._counterBin.hide();

        this._counterBin.connect('style-changed', () => {
            let themeNode = this._counterBin.get_theme_node();
            this._counterBin.translation_x = themeNode.get_length('-shell-counter-overlap-x');
            this._counterBin.translation_y = themeNode.get_length('-shell-counter-overlap-y');
        });

        this.add_actor(this._counterBin);

        this._countUpdatedId = this._source.connect('count-updated', this._updateCount.bind(this));
        this._updateCount();

        this.connect('destroy', () => {
            this._source.disconnect(this._countUpdatedId);
        });
    }

    vfunc_allocate(box, flags) {
        super.vfunc_allocate(box, flags);

        let childBox = new Clutter.ActorBox();

        let [minWidth, minHeight, naturalWidth, naturalHeight] = this._counterBin.get_preferred_size();
        let direction = this.get_text_direction();

        if (direction == Clutter.TextDirection.LTR) {
            // allocate on the right in LTR
            childBox.x1 = box.x2 - naturalWidth;
            childBox.x2 = box.x2;
        } else {
            // allocate on the left in RTL
            childBox.x1 = 0;
            childBox.x2 = naturalWidth;
        }

        childBox.y1 = box.y2 - naturalHeight;
        childBox.y2 = box.y2;

        this._counterBin.allocate(childBox, flags);
    }

    _updateCount() {
        if (this._actorDestroyed)
            return;

        this._counterBin.visible = this._source.countVisible;

        let text;
        if (this._source.count < 100)
            text = this._source.count.toString();
        else
            text = String.fromCharCode(0x22EF); // midline horizontal ellipsis

        this._counterLabel.set_text(text);
    }
});

var Source = class Source {
    constructor(title, iconName) {
        this.SOURCE_ICON_SIZE = 48;

        this.title = title;
        this.iconName = iconName;

        this.isChat = false;

        this.notifications = [];

        this._policy = null;
    }

    get policy() {
        if (!this._policy)
            this._policy = this._createPolicy();
        return this._policy;
    }

    set policy(policy) {
        if (this._policy)
            this._policy.destroy();
        this._policy = policy;
    }

    get count() {
        return this.notifications.length;
    }

    get unseenCount() {
        return this.notifications.filter(n => !n.acknowledged).length;
    }

    get countVisible() {
        return this.count > 1;
    }

    countUpdated() {
        this.emit('count-updated');
    }

    _createPolicy() {
        return new NotificationGenericPolicy();
    }

    get narrowestPrivacyScope() {
        return this.notifications.every(n => n.privacyScope == PrivacyScope.SYSTEM) ? PrivacyScope.SYSTEM
                                                                                    : PrivacyScope.USER;
    }

    setTitle(newTitle) {
        this.title = newTitle;
        this.emit('title-changed');
    }

    createBanner(notification) {
        return new NotificationBanner(notification);
    }

    // Called to create a new icon actor.
    // Provides a sane default implementation, override if you need
    // something more fancy.
    createIcon(size) {
        return new St.Icon({ gicon: this.getIcon(),
                             icon_size: size });
    }

    getIcon() {
        return new Gio.ThemedIcon({ name: this.iconName });
    }

    _onNotificationDestroy(notification) {
        let index = this.notifications.indexOf(notification);
        if (index < 0)
            return;

        this.notifications.splice(index, 1);
        if (this.notifications.length == 0)
            this.destroy();

        this.countUpdated();
    }

    pushNotification(notification) {
        if (this.notifications.indexOf(notification) >= 0)
            return;

        while (this.notifications.length >= MAX_NOTIFICATIONS_PER_SOURCE)
            this.notifications.shift().destroy(NotificationDestroyedReason.EXPIRED);

        notification.connect('destroy', this._onNotificationDestroy.bind(this));
        notification.connect('acknowledged-changed', this.countUpdated.bind(this));
        this.notifications.push(notification);
        this.emit('notification-added', notification);

        this.countUpdated();
    }

    notify(notification) {
        notification.acknowledged = false;
        this.pushNotification(notification);

        if (this.policy.showBanners || notification.urgency == Urgency.CRITICAL) {
            this.emit('notify', notification);
        } else {
            notification.playSound();
        }
    }

    destroy(reason) {
        this.policy.destroy();

        let notifications = this.notifications;
        this.notifications = [];

        for (let i = 0; i < notifications.length; i++)
            notifications[i].destroy(reason);

        this.emit('destroy', reason);
    }

    iconUpdated() {
        this.emit('icon-updated');
    }

    // To be overridden by subclasses
    open() {
    }

    destroyNonResidentNotifications() {
        for (let i = this.notifications.length - 1; i >= 0; i--)
            if (!this.notifications[i].resident)
                this.notifications[i].destroy();

        this.countUpdated();
    }
};
Signals.addSignalMethods(Source.prototype);

var MessageTray = class MessageTray {
    constructor() {
        this._presence = new GnomeSession.Presence((proxy, error) => {
            this._onStatusChanged(proxy.status);
        });
        this._busy = false;
        this._bannerBlocked = false;
        this._presence.connectSignal('StatusChanged', (proxy, senderName, [status]) => {
            this._onStatusChanged(status);
        });

        global.stage.connect('enter-event', (a, ev) => {
            // HACK: St uses ClutterInputDevice for hover tracking, which
            // misses relevant X11 events when untracked actors are
            // involved (read: the notification banner in normal mode),
            // so fix up Clutter's view of the pointer position in
            // that case.
            let related = ev.get_related();
            if (!related || this.actor.contains(related))
                global.sync_pointer();
        });

        this.actor = new St.Widget({ visible: false,
                                     clip_to_allocation: true,
                                     layout_manager: new Clutter.BinLayout() });
        let constraint = new Layout.MonitorConstraint({ primary: true });
        Main.layoutManager.panelBox.bind_property('visible',
                                                  constraint, 'work-area',
                                                  GObject.BindingFlags.SYNC_CREATE);
        this.actor.add_constraint(constraint);

        this._bannerBin = new St.Widget({ name: 'notification-container',
                                          reactive: true,
                                          track_hover: true,
                                          y_align: Clutter.ActorAlign.START,
                                          x_align: Clutter.ActorAlign.CENTER,
                                          y_expand: true,
                                          x_expand: true,
                                          layout_manager: new Clutter.BinLayout() });
        this._bannerBin.connect('key-release-event',
                                this._onNotificationKeyRelease.bind(this));
        this._bannerBin.connect('notify::hover',
                                this._onNotificationHoverChanged.bind(this));
        this.actor.add_actor(this._bannerBin);

        this._notificationFocusGrabber = new FocusGrabber(this._bannerBin);
        this._notificationQueue = [];
        this._notification = null;
        this._banner = null;
        this._bannerClickedId = 0;

        this._userActiveWhileNotificationShown = false;

        this.idleMonitor = Meta.IdleMonitor.get_core();

        this._useLongerNotificationLeftTimeout = false;

        // pointerInNotification is sort of a misnomer -- it tracks whether
        // a message tray notification should expand. The value is
        // partially driven by the hover state of the notification, but has
        // a lot of complex state related to timeouts and the current
        // state of the pointer when a notification pops up.
        this._pointerInNotification = false;

        // This tracks this._bannerBin.hover and is used to fizzle
        // out non-changing hover notifications in onNotificationHoverChanged.
        this._notificationHovered = false;

        this._notificationState = State.HIDDEN;
        this._notificationTimeoutId = 0;
        this._notificationRemoved = false;

        Main.layoutManager.addChrome(this.actor, { affectsInputRegion: false });
        Main.layoutManager.trackChrome(this._bannerBin, { affectsInputRegion: true });

        global.display.connect('in-fullscreen-changed', this._updateState.bind(this));

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));

        Main.overview.connect('window-drag-begin',
                              this._onDragBegin.bind(this));
        Main.overview.connect('window-drag-cancelled',
                              this._onDragEnd.bind(this));
        Main.overview.connect('window-drag-end',
                              this._onDragEnd.bind(this));

        Main.overview.connect('item-drag-begin',
                              this._onDragBegin.bind(this));
        Main.overview.connect('item-drag-cancelled',
                              this._onDragEnd.bind(this));
        Main.overview.connect('item-drag-end',
                              this._onDragEnd.bind(this));

        Main.xdndHandler.connect('drag-begin',
                                 this._onDragBegin.bind(this));
        Main.xdndHandler.connect('drag-end',
                                 this._onDragEnd.bind(this));

        Main.wm.addKeybinding('focus-active-notification',
                              new Gio.Settings({ schema_id: SHELL_KEYBINDINGS_SCHEMA }),
                              Meta.KeyBindingFlags.NONE,
                              Shell.ActionMode.NORMAL |
                              Shell.ActionMode.OVERVIEW,
                              this._expandActiveNotification.bind(this));

        this._sources = new Map();

        this._sessionUpdated();
    }

    _sessionUpdated() {
        this._updateState();
    }

    _onDragBegin() {
        Shell.util_set_hidden_from_pick(this.actor, true);
    }

    _onDragEnd() {
        Shell.util_set_hidden_from_pick(this.actor, false);
    }

    get bannerAlignment() {
        return this._bannerBin.get_x_align();
    }

    set bannerAlignment(align) {
        this._bannerBin.set_x_align(align);
    }

    _onNotificationKeyRelease(actor, event) {
        if (event.get_key_symbol() == Clutter.KEY_Escape && event.get_state() == 0) {
            this._expireNotification();
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _expireNotification() {
        this._notificationExpired = true;
        this._updateState();
    }

    get queueCount() {
        return this._notificationQueue.length;
    }

    set bannerBlocked(v) {
        if (this._bannerBlocked == v)
            return;
        this._bannerBlocked = v;
        this._updateState();
    }

    contains(source) {
        return this._sources.has(source);
    }

    add(source) {
        if (this.contains(source)) {
            log('Trying to re-add source ' + source.title);
            return;
        }

        // Register that we got a notification for this source
        source.policy.store();

        source.policy.connect('enable-changed', () => {
            this._onSourceEnableChanged(source.policy, source);
        });
        source.policy.connect('policy-changed', this._updateState.bind(this));
        this._onSourceEnableChanged(source.policy, source);
    }

    _addSource(source) {
        let obj = {
            source: source,
            notifyId: 0,
            destroyId: 0,
        };

        this._sources.set(source, obj);

        obj.notifyId = source.connect('notify', this._onNotify.bind(this));
        obj.destroyId = source.connect('destroy', this._onSourceDestroy.bind(this));

        this.emit('source-added', source);
    }

    _removeSource(source) {
        let obj = this._sources.get(source);
        this._sources.delete(source);

        source.disconnect(obj.notifyId);
        source.disconnect(obj.destroyId);

        this.emit('source-removed', source);
    }

    getSources() {
        return [...this._sources.keys()];
    }

    _onSourceEnableChanged(policy, source) {
        let wasEnabled = this.contains(source);
        let shouldBeEnabled = policy.enable;

        if (wasEnabled != shouldBeEnabled) {
            if (shouldBeEnabled)
                this._addSource(source);
            else
                this._removeSource(source);
        }
    }

    _onSourceDestroy(source) {
        this._removeSource(source);
    }

    _onNotificationDestroy(notification) {
        if (this._notification == notification && (this._notificationState == State.SHOWN || this._notificationState == State.SHOWING)) {
            this._updateNotificationTimeout(0);
            this._notificationRemoved = true;
            this._updateState();
            return;
        }

        let index = this._notificationQueue.indexOf(notification);
        if (index != -1) {
            this._notificationQueue.splice(index, 1);
            this.emit('queue-changed');
        }
    }

    _onNotify(source, notification) {
        if (this._notification == notification) {
            // If a notification that is being shown is updated, we update
            // how it is shown and extend the time until it auto-hides.
            // If a new notification is updated while it is being hidden,
            // we stop hiding it and show it again.
            this._updateShowingNotification();
        } else if (this._notificationQueue.indexOf(notification) < 0) {
            // If the queue is "full", we skip banner mode and just show a small
            // indicator in the panel; however do make an exception for CRITICAL
            // notifications, as only banner mode allows expansion.
            let bannerCount = this._notification ? 1 : 0;
            let full = (this.queueCount + bannerCount >= MAX_NOTIFICATIONS_IN_QUEUE);
            if (!full || notification.urgency == Urgency.CRITICAL) {
                notification.connect('destroy',
                                     this._onNotificationDestroy.bind(this));
                this._notificationQueue.push(notification);
                this._notificationQueue.sort(
                    (n1, n2) => n2.urgency - n1.urgency
                );
                this.emit('queue-changed');
            }
        }
        this._updateState();
    }

    _resetNotificationLeftTimeout() {
        this._useLongerNotificationLeftTimeout = false;
        if (this._notificationLeftTimeoutId) {
            Mainloop.source_remove(this._notificationLeftTimeoutId);
            this._notificationLeftTimeoutId = 0;
            this._notificationLeftMouseX = -1;
            this._notificationLeftMouseY = -1;
        }
    }

    _onNotificationHoverChanged() {
        if (this._bannerBin.hover == this._notificationHovered)
            return;

        this._notificationHovered = this._bannerBin.hover;
        if (this._notificationHovered) {
            this._resetNotificationLeftTimeout();

            if (this._showNotificationMouseX >= 0) {
                let actorAtShowNotificationPosition =
                    global.stage.get_actor_at_pos(Clutter.PickMode.ALL, this._showNotificationMouseX, this._showNotificationMouseY);
                this._showNotificationMouseX = -1;
                this._showNotificationMouseY = -1;
                // Don't set this._pointerInNotification to true if the pointer was initially in the area where the notification
                // popped up. That way we will not be expanding notifications that happen to pop up over the pointer
                // automatically. Instead, the user is able to expand the notification by mousing away from it and then
                // mousing back in. Because this is an expected action, we set the boolean flag that indicates that a longer
                // timeout should be used before popping down the notification.
                if (this._bannerBin.contains(actorAtShowNotificationPosition)) {
                    this._useLongerNotificationLeftTimeout = true;
                    return;
                }
            }

            this._pointerInNotification = true;
            this._updateState();
        } else {
            // We record the position of the mouse the moment it leaves the tray. These coordinates are used in
            // this._onNotificationLeftTimeout() to determine if the mouse has moved far enough during the initial timeout for us
            // to consider that the user intended to leave the tray and therefore hide the tray. If the mouse is still
            // close to its previous position, we extend the timeout once.
            let [x, y, mods] = global.get_pointer();
            this._notificationLeftMouseX = x;
            this._notificationLeftMouseY = y;

            // We wait just a little before hiding the message tray in case the user quickly moves the mouse back into it.
            // We wait for a longer period if the notification popped up where the mouse pointer was already positioned.
            // That gives the user more time to mouse away from the notification and mouse back in in order to expand it.
            let timeout = this._useLongerNotificationLeftTimeout ? LONGER_HIDE_TIMEOUT * 1000 : HIDE_TIMEOUT * 1000;
            this._notificationLeftTimeoutId = Mainloop.timeout_add(timeout, this._onNotificationLeftTimeout.bind(this));
            GLib.Source.set_name_by_id(this._notificationLeftTimeoutId, '[gnome-shell] this._onNotificationLeftTimeout');
        }
    }

    _onStatusChanged(status) {
        if (status == GnomeSession.PresenceStatus.BUSY) {
            // remove notification and allow the summary to be closed now
            this._updateNotificationTimeout(0);
            this._busy = true;
        } else if (status != GnomeSession.PresenceStatus.IDLE) {
            // We preserve the previous value of this._busy if the status turns to IDLE
            // so that we don't start showing notifications queued during the BUSY state
            // as the screensaver gets activated.
            this._busy = false;
        }

        this._updateState();
    }

    _onNotificationLeftTimeout() {
        let [x, y, mods] = global.get_pointer();
        // We extend the timeout once if the mouse moved no further than MOUSE_LEFT_ACTOR_THRESHOLD to either side.
        if (this._notificationLeftMouseX > -1 &&
            y < this._notificationLeftMouseY + MOUSE_LEFT_ACTOR_THRESHOLD &&
            y > this._notificationLeftMouseY - MOUSE_LEFT_ACTOR_THRESHOLD &&
            x < this._notificationLeftMouseX + MOUSE_LEFT_ACTOR_THRESHOLD &&
            x > this._notificationLeftMouseX - MOUSE_LEFT_ACTOR_THRESHOLD) {
            this._notificationLeftMouseX = -1;
            this._notificationLeftTimeoutId = Mainloop.timeout_add(LONGER_HIDE_TIMEOUT * 1000,
                                                             this._onNotificationLeftTimeout.bind(this));
            GLib.Source.set_name_by_id(this._notificationLeftTimeoutId, '[gnome-shell] this._onNotificationLeftTimeout');
        } else {
            this._notificationLeftTimeoutId = 0;
            this._useLongerNotificationLeftTimeout = false;
            this._pointerInNotification = false;
            this._updateNotificationTimeout(0);
            this._updateState();
        }
        return GLib.SOURCE_REMOVE;
    }

    _escapeTray() {
        this._pointerInNotification = false;
        this._updateNotificationTimeout(0);
        this._updateState();
    }

    // All of the logic for what happens when occurs here; the various
    // event handlers merely update variables such as
    // 'this._pointerInNotification', 'this._traySummoned', etc, and
    // _updateState() figures out what (if anything) needs to be done
    // at the present time.
    _updateState() {
        let hasMonitor = Main.layoutManager.primaryMonitor != null;
        this.actor.visible = !this._bannerBlocked && hasMonitor && this._banner != null;
        if (this._bannerBlocked || !hasMonitor)
            return;

        // If our state changes caused _updateState to be called,
        // just exit now to prevent reentrancy issues.
        if (this._updatingState)
            return;

        this._updatingState = true;

        // Filter out acknowledged notifications.
        let changed = false;
        this._notificationQueue = this._notificationQueue.filter(n => {
            changed = changed || n.acknowledged;
            return !n.acknowledged;
        });

        if (changed)
            this.emit('queue-changed');

        let hasNotifications = Main.sessionMode.hasNotifications;

        if (this._notificationState == State.HIDDEN) {
            let nextNotification = this._notificationQueue[0] || null;
            if (hasNotifications && nextNotification) {
                let limited = this._busy || Main.layoutManager.primaryMonitor.inFullscreen;
                let showNextNotification = (!limited || nextNotification.forFeedback || nextNotification.urgency == Urgency.CRITICAL);
                if (showNextNotification)
                    this._showNotification();
            }
        } else if (this._notificationState == State.SHOWN) {
            let expired = (this._userActiveWhileNotificationShown &&
                           this._notificationTimeoutId == 0 &&
                           this._notification.urgency != Urgency.CRITICAL &&
                           !this._banner.focused &&
                           !this._pointerInNotification) || this._notificationExpired;
            let mustClose = (this._notificationRemoved || !hasNotifications || expired);

            if (mustClose) {
                let animate = hasNotifications && !this._notificationRemoved;
                this._hideNotification(animate);
            } else if (this._pointerInNotification && !this._banner.expanded) {
                this._expandBanner(false);
            } else if (this._pointerInNotification) {
                this._ensureBannerFocused();
            }
        }

        this._updatingState = false;

        // Clean transient variables that are used to communicate actions
        // to updateState()
        this._notificationExpired = false;
    }

    _tween(actor, statevar, value, params) {
        let onComplete = params.onComplete;
        let onCompleteScope = params.onCompleteScope;
        let onCompleteParams = params.onCompleteParams;

        params.onComplete = this._tweenComplete;
        params.onCompleteScope = this;
        params.onCompleteParams = [statevar, value, onComplete, onCompleteScope, onCompleteParams];

        // Remove other tweens that could mess with the state machine
        Tweener.removeTweens(actor);
        Tweener.addTween(actor, params);

        let valuing = (value == State.SHOWN) ? State.SHOWING : State.HIDING;
        this[statevar] = valuing;
    }

    _tweenComplete(statevar, value, onComplete, onCompleteScope, onCompleteParams) {
        this[statevar] = value;
        if (onComplete)
            onComplete.apply(onCompleteScope, onCompleteParams);
        this._updateState();
    }

    _clampOpacity() {
        this._bannerBin.opacity = Math.max(0, Math.min(this._bannerBin._opacity, 255));
    }

    _onIdleMonitorBecameActive() {
        this._userActiveWhileNotificationShown = true;
        this._updateNotificationTimeout(2000);
        this._updateState();
    }

    _showNotification() {
        this._notification = this._notificationQueue.shift();
        this.emit('queue-changed');

        this._userActiveWhileNotificationShown = this.idleMonitor.get_idletime() <= IDLE_TIME;
        if (!this._userActiveWhileNotificationShown) {
            // If the user isn't active, set up a watch to let us know
            // when the user becomes active.
            this.idleMonitor.add_user_active_watch(this._onIdleMonitorBecameActive.bind(this));
        }

        this._banner = this._notification.createBanner();
        this._bannerClickedId = this._banner.connect('done-displaying',
                                                     this._escapeTray.bind(this));
        this._bannerUnfocusedId = this._banner.connect('unfocused', () => {
            this._updateState();
        });

        this._bannerBin.add_actor(this._banner.actor);

        this._bannerBin._opacity = 0;
        this._bannerBin.opacity = 0;
        this._bannerBin.y = -this._banner.actor.height;
        this.actor.show();

        Meta.disable_unredirect_for_display(global.display);
        this._updateShowingNotification();

        let [x, y, mods] = global.get_pointer();
        // We save the position of the mouse at the time when we started showing the notification
        // in order to determine if the notification popped up under it. We make that check if
        // the user starts moving the mouse and _onNotificationHoverChanged() gets called. We don't
        // expand the notification if it just happened to pop up under the mouse unless the user
        // explicitly mouses away from it and then mouses back in.
        this._showNotificationMouseX = x;
        this._showNotificationMouseY = y;
        // We save the coordinates of the mouse at the time when we started showing the notification
        // and then we update it in _notificationTimeout(). We don't pop down the notification if
        // the mouse is moving towards it or within it.
        this._lastSeenMouseX = x;
        this._lastSeenMouseY = y;

        this._resetNotificationLeftTimeout();
    }

    _updateShowingNotification() {
        this._notification.acknowledged = true;
        this._notification.playSound();

        // We auto-expand notifications with CRITICAL urgency, or for which the relevant setting
        // is on in the control center.
        if (this._notification.urgency == Urgency.CRITICAL ||
            this._notification.source.policy.forceExpanded)
            this._expandBanner(true);

        // We tween all notifications to full opacity. This ensures that both new notifications and
        // notifications that might have been in the process of hiding get full opacity.
        //
        // We tween any notification showing in the banner mode to the appropriate height
        // (which is banner height or expanded height, depending on the notification state)
        // This ensures that both new notifications and notifications in the banner mode that might
        // have been in the process of hiding are shown with the correct height.
        //
        // We use this._showNotificationCompleted() onComplete callback to extend the time the updated
        // notification is being shown.

        let tweenParams = { y: 0,
                            _opacity: 255,
                            time: ANIMATION_TIME,
                            transition: 'easeOutBack',
                            onUpdate: this._clampOpacity,
                            onUpdateScope: this,
                            onComplete: this._showNotificationCompleted,
                            onCompleteScope: this
                          };

        this._tween(this._bannerBin, '_notificationState', State.SHOWN, tweenParams);
    }

    _showNotificationCompleted() {
        if (this._notification.urgency != Urgency.CRITICAL)
            this._updateNotificationTimeout(NOTIFICATION_TIMEOUT * 1000);
    }

    _updateNotificationTimeout(timeout) {
        if (this._notificationTimeoutId) {
            Mainloop.source_remove(this._notificationTimeoutId);
            this._notificationTimeoutId = 0;
        }
        if (timeout > 0) {
            this._notificationTimeoutId =
                Mainloop.timeout_add(timeout,
                                     this._notificationTimeout.bind(this));
            GLib.Source.set_name_by_id(this._notificationTimeoutId, '[gnome-shell] this._notificationTimeout');
        }
    }

    _notificationTimeout() {
        let [x, y, mods] = global.get_pointer();
        if (y < this._lastSeenMouseY - 10 && !this._notificationHovered) {
            // The mouse is moving towards the notification, so don't
            // hide it yet. (We just create a new timeout (and destroy
            // the old one) each time because the bookkeeping is
            // simpler.)
            this._updateNotificationTimeout(1000);
        } else if (this._useLongerNotificationLeftTimeout && !this._notificationLeftTimeoutId &&
                  (x != this._lastSeenMouseX || y != this._lastSeenMouseY)) {
            // Refresh the timeout if the notification originally
            // popped up under the pointer, and the pointer is hovering
            // inside it.
            this._updateNotificationTimeout(1000);
        } else {
            this._notificationTimeoutId = 0;
            this._updateState();
        }

        this._lastSeenMouseX = x;
        this._lastSeenMouseY = y;
        return GLib.SOURCE_REMOVE;
    }

    _hideNotification(animate) {
        this._notificationFocusGrabber.ungrabFocus();

        if (this._bannerClickedId) {
            this._banner.disconnect(this._bannerClickedId);
            this._bannerClickedId = 0;
        }
        if (this._bannerUnfocusedId) {
            this._banner.disconnect(this._bannerUnfocusedId);
            this._bannerUnfocusedId = 0;
        }

        this._resetNotificationLeftTimeout();

        if (animate) {
            this._tween(this._bannerBin, '_notificationState', State.HIDDEN,
                        { y: -this._bannerBin.height,
                          _opacity: 0,
                          time: ANIMATION_TIME,
                          transition: 'easeOutBack',
                          onUpdate: this._clampOpacity,
                          onUpdateScope: this,
                          onComplete: this._hideNotificationCompleted,
                          onCompleteScope: this
                        });
        } else {
            Tweener.removeTweens(this._bannerBin);
            this._bannerBin.y = -this._bannerBin.height;
            this._bannerBin.opacity = 0;
            this._notificationState = State.HIDDEN;
            this._hideNotificationCompleted();
        }
    }

    _hideNotificationCompleted() {
        let notification = this._notification;
        this._notification = null;
        if (notification.isTransient)
            notification.destroy(NotificationDestroyedReason.EXPIRED);

        this._pointerInNotification = false;
        this._notificationRemoved = false;
        Meta.enable_unredirect_for_display(global.display);

        this._banner.actor.destroy();
        this._banner = null;
        this.actor.hide();
    }

    _expandActiveNotification() {
        if (!this._banner)
            return;

        this._expandBanner(false);
    }

    _expandBanner(autoExpanding) {
        // Don't animate changes in notifications that are auto-expanding.
        this._banner.expand(!autoExpanding);

        // Don't focus notifications that are auto-expanding.
        if (!autoExpanding)
            this._ensureBannerFocused();
    }

    _ensureBannerFocused() {
        this._notificationFocusGrabber.grabFocus();
    }
};
Signals.addSignalMethods(MessageTray.prototype);

var SystemNotificationSource = class SystemNotificationSource extends Source {
    constructor() {
        super(_("System Information"), 'dialog-information-symbolic');
    }

    open() {
        this.destroy();
    }
};
(uuay)misc/*9^z<Wl
:`fV%!-�backgroundMenu.jsV	// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, St } = imports.gi;

const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;

var BackgroundMenu = class BackgroundMenu extends PopupMenu.PopupMenu {
    constructor(layoutManager) {
        super(layoutManager.dummyCursor, 0, St.Side.TOP);

        this.addSettingsAction(_("Change Background…"), 'gnome-background-panel.desktop');
        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        this.addSettingsAction(_("Display Settings"), 'gnome-display-panel.desktop');
        this.addSettingsAction(_("Settings"), 'gnome-control-center.desktop');

        this.actor.add_style_class_name('background-menu');

        layoutManager.uiGroup.add_actor(this.actor);
        this.actor.hide();
    }
};

function addBackgroundMenu(actor, layoutManager) {
    actor.reactive = true;
    actor._backgroundMenu = new BackgroundMenu(layoutManager);
    actor._backgroundManager = new PopupMenu.PopupMenuManager({ actor: actor });
    actor._backgroundManager.addMenu(actor._backgroundMenu);

    function openMenu(x, y) {
        Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
        actor._backgroundMenu.open(BoxPointer.PopupAnimation.NONE);
    }

    let clickAction = new Clutter.ClickAction();
    clickAction.connect('long-press', (action, actor, state) => {
        if (state == Clutter.LongPressState.QUERY)
            return ((action.get_button() == 0 ||
                     action.get_button() == 1) &&
                    !actor._backgroundMenu.isOpen);
        if (state == Clutter.LongPressState.ACTIVATE) {
            let [x, y] = action.get_coords();
            openMenu(x, y);
            actor._backgroundManager.ignoreRelease();
        }
        return true;
    });
    clickAction.connect('clicked', action => {
        if (action.get_button() == 3) {
            let [x, y] = action.get_coords();
            openMenu(x, y);
        }
    });
    actor.add_action(clickAction);

    let grabOpBeginId = global.display.connect('grab-op-begin', () => {
        clickAction.release();
    });

    actor.connect('destroy', () => {
        actor._backgroundMenu.destroy();
        actor._backgroundMenu = null;
        actor._backgroundManager = null;
        global.display.disconnect(grabOpBeginId);
    });
}
(uuay)remoteSearch.js�,// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { GdkPixbuf, Gio, GLib, Shell, St } = imports.gi;

const FileUtils = imports.misc.fileUtils;

const KEY_FILE_GROUP = 'Shell Search Provider';

const SearchProviderIface = `
<node>
<interface name="org.gnome.Shell.SearchProvider">
<method name="GetInitialResultSet">
    <arg type="as" direction="in" />
    <arg type="as" direction="out" />
</method>
<method name="GetSubsearchResultSet">
    <arg type="as" direction="in" />
    <arg type="as" direction="in" />
    <arg type="as" direction="out" />
</method>
<method name="GetResultMetas">
    <arg type="as" direction="in" />
    <arg type="aa{sv}" direction="out" />
</method>
<method name="ActivateResult">
    <arg type="s" direction="in" />
</method>
</interface>
</node>`;

const SearchProvider2Iface = `
<node>
<interface name="org.gnome.Shell.SearchProvider2">
<method name="GetInitialResultSet">
    <arg type="as" direction="in" />
    <arg type="as" direction="out" />
</method>
<method name="GetSubsearchResultSet">
    <arg type="as" direction="in" />
    <arg type="as" direction="in" />
    <arg type="as" direction="out" />
</method>
<method name="GetResultMetas">
    <arg type="as" direction="in" />
    <arg type="aa{sv}" direction="out" />
</method>
<method name="ActivateResult">
    <arg type="s" direction="in" />
    <arg type="as" direction="in" />
    <arg type="u" direction="in" />
</method>
<method name="LaunchSearch">
    <arg type="as" direction="in" />
    <arg type="u" direction="in" />
</method>
</interface>
</node>`;

var SearchProviderProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProviderIface);
var SearchProvider2ProxyInfo = Gio.DBusInterfaceInfo.new_for_xml(SearchProvider2Iface);

function loadRemoteSearchProviders(searchSettings, callback) {
    let objectPaths = {};
    let loadedProviders = [];

    function loadRemoteSearchProvider(file) {
        let keyfile = new GLib.KeyFile();
        let path = file.get_path();

        try {
            keyfile.load_from_file(path, 0);
        } catch(e) {
            return;
        }

        if (!keyfile.has_group(KEY_FILE_GROUP))
            return;

        let remoteProvider;
        try {
            let group = KEY_FILE_GROUP;
            let busName = keyfile.get_string(group, 'BusName');
            let objectPath = keyfile.get_string(group, 'ObjectPath');

            if (objectPaths[objectPath])
                return;

            let appInfo = null;
            try {
                let desktopId = keyfile.get_string(group, 'DesktopId');
                appInfo = Gio.DesktopAppInfo.new(desktopId);
            } catch (e) {
                log('Ignoring search provider ' + path + ': missing DesktopId');
                return;
            }

            let autoStart = true;
            try {
                autoStart = keyfile.get_boolean(group, 'AutoStart');
            } catch(e) {
                // ignore error
            }

            let version = '1';
            try {
                version = keyfile.get_string(group, 'Version');
            } catch (e) {
                // ignore error
            }

            if (version >= 2)
                remoteProvider = new RemoteSearchProvider2(appInfo, busName, objectPath, autoStart);
            else
                remoteProvider = new RemoteSearchProvider(appInfo, busName, objectPath, autoStart);

            remoteProvider.defaultEnabled = true;
            try {
                remoteProvider.defaultEnabled = !keyfile.get_boolean(group, 'DefaultDisabled');
            } catch(e) {
                // ignore error
            }

            objectPaths[objectPath] = remoteProvider;
            loadedProviders.push(remoteProvider);
        } catch(e) {
            log('Failed to add search provider %s: %s'.format(path, e.toString()));
        }
    }

    if (searchSettings.get_boolean('disable-external')) {
        callback([]);
        return;
    }

    FileUtils.collectFromDatadirs('search-providers', false, loadRemoteSearchProvider);

    let sortOrder = searchSettings.get_strv('sort-order');

    // Special case gnome-control-center to be always active and always first
    sortOrder.unshift('gnome-control-center.desktop');

    loadedProviders = loadedProviders.filter(provider => {
        let appId = provider.appInfo.get_id();

        if (provider.defaultEnabled) {
            let disabled = searchSettings.get_strv('disabled');
            return disabled.indexOf(appId) == -1;
        } else {
            let enabled = searchSettings.get_strv('enabled');
            return enabled.indexOf(appId) != -1;
        }
    });

    loadedProviders.sort((providerA, providerB) => {
        let idxA, idxB;
        let appIdA, appIdB;

        appIdA = providerA.appInfo.get_id();
        appIdB = providerB.appInfo.get_id();

        idxA = sortOrder.indexOf(appIdA);
        idxB = sortOrder.indexOf(appIdB);

        // if no provider is found in the order, use alphabetical order
        if ((idxA == -1) && (idxB == -1)) {
            let nameA = providerA.appInfo.get_name();
            let nameB = providerB.appInfo.get_name();

            return GLib.utf8_collate(nameA, nameB);
        }

        // if providerA isn't found, it's sorted after providerB
        if (idxA == -1)
            return 1;

        // if providerB isn't found, it's sorted after providerA
        if (idxB == -1)
            return -1;

        // finally, if both providers are found, return their order in the list
        return (idxA - idxB);
    });

    callback(loadedProviders);
}

var RemoteSearchProvider = class {
    constructor(appInfo, dbusName, dbusPath, autoStart, proxyInfo) {
        if (!proxyInfo)
            proxyInfo = SearchProviderProxyInfo;

        let g_flags = Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES;
        if (autoStart)
            g_flags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION;
        else
            g_flags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START;

        this.proxy = new Gio.DBusProxy({ g_bus_type: Gio.BusType.SESSION,
                                         g_name: dbusName,
                                         g_object_path: dbusPath,
                                         g_interface_info: proxyInfo,
                                         g_interface_name: proxyInfo.name,
                                         g_flags });
        this.proxy.init_async(GLib.PRIORITY_DEFAULT, null, null);

        this.appInfo = appInfo;
        this.id = appInfo.get_id();
        this.isRemoteProvider = true;
        this.canLaunchSearch = false;
    }

    createIcon(size, meta) {
        let gicon = null;
        let icon = null;

        if (meta['icon']) {
            gicon = Gio.icon_deserialize(meta['icon']);
        } else if (meta['gicon']) {
            gicon = Gio.icon_new_for_string(meta['gicon']);
        } else if (meta['icon-data']) {
            let [width, height, rowStride, hasAlpha,
                 bitsPerSample, nChannels, data] = meta['icon-data'];
            gicon = Shell.util_create_pixbuf_from_data(data, GdkPixbuf.Colorspace.RGB, hasAlpha,
                                                       bitsPerSample, width, height, rowStride);
        }

        if (gicon)
            icon = new St.Icon({ gicon: gicon,
                                 icon_size: size });
        return icon;
    }

    filterResults(results, maxNumber) {
        if (results.length <= maxNumber)
            return results;

        let regularResults = results.filter(r => !r.startsWith('special:'));
        let specialResults = results.filter(r => r.startsWith('special:'));

        return regularResults.slice(0, maxNumber).concat(specialResults.slice(0, maxNumber));
    }

    _getResultsFinished(results, error, callback) {
        if (error) {
            if (error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;

            log('Received error from DBus search provider %s: %s'.format(this.id, String(error)));
            callback([]);
            return;
        }

        callback(results[0]);
    }

    getInitialResultSet(terms, callback, cancellable) {
        this.proxy.GetInitialResultSetRemote(terms,
                                             (results, error) => {
                                                 this._getResultsFinished(results, error, callback);
                                             },
                                             cancellable);
    }

    getSubsearchResultSet(previousResults, newTerms, callback, cancellable) {
        this.proxy.GetSubsearchResultSetRemote(previousResults, newTerms,
                                               (results, error) => {
                                                   this._getResultsFinished(results, error, callback);
                                               },
                                               cancellable);
    }

    _getResultMetasFinished(results, error, callback) {
        if (error) {
            if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                log('Received error from DBus search provider %s during GetResultMetas: %s'.format(this.id, String(error)));
            callback([]);
            return;
        }
        let metas = results[0];
        let resultMetas = [];
        for (let i = 0; i < metas.length; i++) {
            for (let prop in metas[i]) {
                // we can use the serialized icon variant directly
                if (prop != 'icon')
                    metas[i][prop] = metas[i][prop].deep_unpack();
            }

            resultMetas.push({ id: metas[i]['id'],
                               name: metas[i]['name'],
                               description: metas[i]['description'],
                               createIcon: size => {
                                   return this.createIcon(size, metas[i]);
                               },
                               clipboardText: metas[i]['clipboardText'] });
        }
        callback(resultMetas);
    }

    getResultMetas(ids, callback, cancellable) {
        this.proxy.GetResultMetasRemote(ids,
                                        (results, error) => {
                                            this._getResultMetasFinished(results, error, callback);
                                        },
                                        cancellable);
    }

    activateResult(id) {
        this.proxy.ActivateResultRemote(id);
    }

    launchSearch(terms) {
        // the provider is not compatible with the new version of the interface, launch
        // the app itself but warn so we can catch the error in logs
        log('Search provider ' + this.appInfo.get_id() + ' does not implement LaunchSearch');
        this.appInfo.launch([], global.create_app_launch_context(0, -1));
    }
};

var RemoteSearchProvider2 = class extends RemoteSearchProvider {
    constructor(appInfo, dbusName, dbusPath, autoStart) {
        super(appInfo, dbusName, dbusPath, autoStart, SearchProvider2ProxyInfo);

        this.canLaunchSearch = true;
    }

    activateResult(id, terms) {
        this.proxy.ActivateResultRemote(id, terms, global.get_current_time());
    }

    launchSearch(terms) {
        this.proxy.LaunchSearchRemote(terms, global.get_current_time());
    }
};
(uuay)status/q
m nep3xui/v�{/~LS��Us]1.y_aMEjb6KH(&)|k7P0=Z'	2I$CN?hJR4�B�#�w,rX"oud@TshellMountOperation.js�V// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, Pango, Shell, St } = imports.gi;
const Signals = imports.signals;

const CheckBox = imports.ui.checkBox;
const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const ModalDialog = imports.ui.modalDialog;
const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;

const { loadInterfaceXML } = imports.misc.fileUtils;

var LIST_ITEM_ICON_SIZE = 48;

const REMEMBER_MOUNT_PASSWORD_KEY = 'remember-mount-password';

/* ------ Common Utils ------- */
function _setLabelText(label, text) {
    if (text) {
        label.set_text(text);
        label.show();
    } else {
        label.set_text('');
        label.hide();
    }
}

function _setButtonsForChoices(dialog, choices) {
    let buttons = [];

    for (let idx = 0; idx < choices.length; idx++) {
        let button = idx;
        buttons.unshift({ label: choices[idx],
                          action: () => { dialog.emit('response', button); }
                        });
    }

    dialog.setButtons(buttons);
}

function _setLabelsForMessage(content, message) {
    let labels = message.split('\n');

    content.title = labels.shift();
    content.body = labels.join('\n');
}

function _createIcon(gicon) {
    return new St.Icon({ gicon: gicon,
                         style_class: 'shell-mount-operation-icon' })
}

/* -------------------------------------------------------- */

var ListItem = class {
    constructor(app) {
        this._app = app;

        let layout = new St.BoxLayout({ vertical: false});

        this.actor = new St.Button({ style_class: 'mount-dialog-app-list-item',
                                     can_focus: true,
                                     child: layout,
                                     reactive: true,
                                     x_align: St.Align.START,
                                     x_fill: true });

        this._icon = this._app.create_icon_texture(LIST_ITEM_ICON_SIZE);

        let iconBin = new St.Bin({ style_class: 'mount-dialog-app-list-item-icon',
                                   child: this._icon });
        layout.add(iconBin);

        this._nameLabel = new St.Label({ text: this._app.get_name(),
                                         style_class: 'mount-dialog-app-list-item-name' });
        let labelBin = new St.Bin({ y_align: St.Align.MIDDLE,
                                    child: this._nameLabel });
        layout.add(labelBin);

        this.actor.connect('clicked', this._onClicked.bind(this));
    }

    _onClicked() {
        this.emit('activate');
        this._app.activate();
    }
};
Signals.addSignalMethods(ListItem.prototype);

var ShellMountOperation = class {
    constructor(source, params) {
        params = Params.parse(params, { existingDialog: null });

        this._dialog = null;
        this._dialogId = 0;
        this._existingDialog = params.existingDialog;
        this._processesDialog = null;

        this.mountOp = new Shell.MountOperation();

        this.mountOp.connect('ask-question',
                             this._onAskQuestion.bind(this));
        this.mountOp.connect('ask-password',
                             this._onAskPassword.bind(this));
        this.mountOp.connect('show-processes-2',
                             this._onShowProcesses2.bind(this));
        this.mountOp.connect('aborted',
                             this.close.bind(this));
        this.mountOp.connect('show-unmount-progress',
                             this._onShowUnmountProgress.bind(this));

        this._gicon = source.get_icon();
    }

    _closeExistingDialog() {
        if (!this._existingDialog)
            return;

        this._existingDialog.close();
        this._existingDialog = null;
    }

    _onAskQuestion(op, message, choices) {
        this._closeExistingDialog();
        this._dialog = new ShellMountQuestionDialog(this._gicon);

        this._dialogId = this._dialog.connect('response',
            (object, choice) => {
                this.mountOp.set_choice(choice);
                this.mountOp.reply(Gio.MountOperationResult.HANDLED);

                this.close();
            });

        this._dialog.update(message, choices);
        this._dialog.open();
    }

    _onAskPassword(op, message, defaultUser, defaultDomain, flags) {
        if (this._existingDialog) {
            this._dialog = this._existingDialog;
            this._dialog.reaskPassword();
        } else {
            this._dialog = new ShellMountPasswordDialog(message, this._gicon, flags);
        }

        this._dialogId = this._dialog.connect('response',
            (object, choice, password, remember) => {
                if (choice == -1) {
                    this.mountOp.reply(Gio.MountOperationResult.ABORTED);
                } else {
                    if (remember)
                        this.mountOp.set_password_save(Gio.PasswordSave.PERMANENTLY);
                    else
                        this.mountOp.set_password_save(Gio.PasswordSave.NEVER);

                    this.mountOp.set_password(password);
                    this.mountOp.reply(Gio.MountOperationResult.HANDLED);
                }
            });
        this._dialog.open();
    }

    close(op) {
        this._closeExistingDialog();
        this._processesDialog = null;

        if (this._dialog) {
            this._dialog.close();
            this._dialog = null;
        }

        if (this._notifier) {
            this._notifier.done();
            this._notifier = null;
        }
    }

    _onShowProcesses2(op) {
        this._closeExistingDialog();

        let processes = op.get_show_processes_pids();
        let choices = op.get_show_processes_choices();
        let message = op.get_show_processes_message();

        if (!this._processesDialog) {
            this._processesDialog = new ShellProcessesDialog(this._gicon);
            this._dialog = this._processesDialog;

            this._dialogId = this._processesDialog.connect('response',
                (object, choice) => {
                    if (choice == -1) {
                        this.mountOp.reply(Gio.MountOperationResult.ABORTED);
                    } else {
                        this.mountOp.set_choice(choice);
                        this.mountOp.reply(Gio.MountOperationResult.HANDLED);
                    }

                    this.close();
                });
            this._processesDialog.open();
        }

        this._processesDialog.update(message, processes, choices);
    }

    _onShowUnmountProgress(op, message, timeLeft, bytesLeft) {
        if (!this._notifier)
            this._notifier = new ShellUnmountNotifier();
            
        if (bytesLeft == 0)
            this._notifier.done(message);
        else
            this._notifier.show(message);
    }

    borrowDialog() {
        if (this._dialogId != 0) {
            this._dialog.disconnect(this._dialogId);
            this._dialogId = 0;
        }

        return this._dialog;
    }
};

var ShellUnmountNotifier = class extends MessageTray.Source {
    constructor() {
        super('', 'media-removable');

        this._notification = null;
        Main.messageTray.add(this);
    }

    show(message) {
        let [header, text] = message.split('\n', 2);

        if (!this._notification) {
            this._notification = new MessageTray.Notification(this, header, text);
            this._notification.setTransient(true);
            this._notification.setUrgency(MessageTray.Urgency.CRITICAL);
        } else {
            this._notification.update(header, text);
        }

        this.notify(this._notification);
    }

    done(message) {
        if (this._notification) {
            this._notification.destroy();
            this._notification = null;
        }

        if (message) {
            let notification = new MessageTray.Notification(this, message, null);
            notification.setTransient(true);

            this.notify(notification);
        }
    }
};

var ShellMountQuestionDialog = class extends ModalDialog.ModalDialog {
    constructor(icon) {
        super({ styleClass: 'mount-dialog' });

        this._content = new Dialog.MessageDialogContent({ icon });
        this.contentLayout.add(this._content, { x_fill: true, y_fill: false });
    }

    update(message, choices) {
        _setLabelsForMessage(this._content, message);
        _setButtonsForChoices(this, choices);
    }
};
Signals.addSignalMethods(ShellMountQuestionDialog.prototype);

var ShellMountPasswordDialog = class extends ModalDialog.ModalDialog {
    constructor(message, icon, flags) {
        let strings = message.split('\n');
        let title = strings.shift() || null;
        let body = strings.shift() || null;
        super({ styleClass: 'prompt-dialog' });

        let content = new Dialog.MessageDialogContent({ icon, title, body });
        this.contentLayout.add_actor(content);

        this._passwordBox = new St.BoxLayout({ vertical: false, style_class: 'prompt-dialog-password-box' });
        content.messageBox.add(this._passwordBox);

        this._passwordLabel = new St.Label(({ style_class: 'prompt-dialog-password-label',
                                              text: _("Password") }));
        this._passwordBox.add(this._passwordLabel, { y_fill: false, y_align: St.Align.MIDDLE });

        this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
                                             text: "",
                                             can_focus: true});
        ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
        this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
        this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
        this._passwordBox.add(this._passwordEntry, {expand: true });
        this.setInitialKeyFocus(this._passwordEntry);

        this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
        content.messageBox.add(this._capsLockWarningLabel);

        this._errorMessageLabel = new St.Label({ style_class: 'prompt-dialog-error-label',
                                                 text: _("Sorry, that didn’t work. Please try again.") });
        this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._errorMessageLabel.clutter_text.line_wrap = true;
        this._errorMessageLabel.hide();
        content.messageBox.add(this._errorMessageLabel);

        if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
            this._rememberChoice = new CheckBox.CheckBox();
            this._rememberChoice.getLabelActor().text = _("Remember Password");
            this._rememberChoice.actor.checked =
                global.settings.get_boolean(REMEMBER_MOUNT_PASSWORD_KEY);
            content.messageBox.add(this._rememberChoice.actor);
        } else {
            this._rememberChoice = null;
        }

        let buttons = [{ label: _("Cancel"),
                         action: this._onCancelButton.bind(this),
                         key:    Clutter.Escape
                       },
                       { label: _("Unlock"),
                         action: this._onUnlockButton.bind(this),
                         default: true
                       }];

        this.setButtons(buttons);
    }

    reaskPassword() {
        this._passwordEntry.set_text('');
        this._errorMessageLabel.show();
    }

    _onCancelButton() {
        this.emit('response', -1, '', false);
    }

    _onUnlockButton() {
        this._onEntryActivate();
    }

    _onEntryActivate() {
        global.settings.set_boolean(REMEMBER_MOUNT_PASSWORD_KEY,
            this._rememberChoice && this._rememberChoice.actor.checked);
        this.emit('response', 1,
            this._passwordEntry.get_text(),
            this._rememberChoice &&
            this._rememberChoice.actor.checked);
    }
};

var ShellProcessesDialog = class extends ModalDialog.ModalDialog {
    constructor(icon) {
        super({ styleClass: 'mount-dialog' });

        this._content = new Dialog.MessageDialogContent({ icon });
        this.contentLayout.add(this._content, { x_fill: true, y_fill: false });

        let scrollView = new St.ScrollView({ style_class: 'mount-dialog-app-list'});
        scrollView.set_policy(St.PolicyType.NEVER,
                              St.PolicyType.AUTOMATIC);
        this.contentLayout.add(scrollView,
                               { x_fill: true,
                                 y_fill: true });
        scrollView.hide();

        this._applicationList = new St.BoxLayout({ vertical: true });
        scrollView.add_actor(this._applicationList);

        this._applicationList.connect('actor-added', () => {
            if (this._applicationList.get_n_children() == 1)
                scrollView.show();
        });

        this._applicationList.connect('actor-removed', () => {
            if (this._applicationList.get_n_children() == 0)
                scrollView.hide();
        });
    }

    _setAppsForPids(pids) {
        // remove all the items
        this._applicationList.destroy_all_children();

        pids.forEach(pid => {
            let tracker = Shell.WindowTracker.get_default();
            let app = tracker.get_app_from_pid(pid);

            if (!app)
                return;

            let item = new ListItem(app);
            this._applicationList.add(item.actor, { x_fill: true });

            item.connect('activate', () => {
                // use -1 to indicate Cancel
                this.emit('response', -1);
            });
        });
    }

    update(message, processes, choices) {
        this._setAppsForPids(processes);
        _setLabelsForMessage(this._content, message);
        _setButtonsForChoices(this, choices);
    }
};
Signals.addSignalMethods(ShellProcessesDialog.prototype);

const GnomeShellMountOpIface = loadInterfaceXML('org.Gtk.MountOperationHandler');

var ShellMountOperationType = {
    NONE: 0,
    ASK_PASSWORD: 1,
    ASK_QUESTION: 2,
    SHOW_PROCESSES: 3
};

var GnomeShellMountOpHandler = class {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(GnomeShellMountOpIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gtk/MountOperationHandler');
        Gio.bus_own_name_on_connection(Gio.DBus.session, 'org.gtk.MountOperationHandler',
                                       Gio.BusNameOwnerFlags.REPLACE, null, null);

        this._dialog = null;
        this._volumeMonitor = Gio.VolumeMonitor.get();

        this._ensureEmptyRequest();
    }

    _ensureEmptyRequest() {
        this._currentId = null;
        this._currentInvocation = null;
        this._currentType = ShellMountOperationType.NONE;
    }

    _clearCurrentRequest(response, details) {
        if (this._currentInvocation) {
            this._currentInvocation.return_value(
                GLib.Variant.new('(ua{sv})', [response, details]));
        }

        this._ensureEmptyRequest();
    }

    _setCurrentRequest(invocation, id, type) {
        let oldId = this._currentId;
        let oldType = this._currentType;
        let requestId = id + '@' + invocation.get_sender();

        this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});

        this._currentInvocation = invocation;
        this._currentId = requestId;
        this._currentType = type;

        if (this._dialog && (oldId == requestId) && (oldType == type))
            return true;

        return false;
    }

    _closeDialog() {
        if (this._dialog) {
            this._dialog.close();
            this._dialog = null;
        }
    }

    _createGIcon(iconName) {
        let realIconName = iconName ? iconName : 'drive-harddisk';
        return new Gio.ThemedIcon({ name: realIconName,
                                    use_default_fallbacks: true });
    }

    /**
     * AskPassword:
     * @id: an opaque ID identifying the object for which the operation is requested
     *      The ID must be unique in the context of the calling process.
     * @message: the message to display
     * @icon_name: the name of an icon to display
     * @default_user: the default username for display
     * @default_domain: the default domain for display
     * @flags: a set of GAskPasswordFlags
     * @response: a GMountOperationResult
     * @response_details: a dictionary containing the response details as
     * entered by the user. The dictionary MAY contain the following properties:
     *   - "password" -> (s): a password to be used to complete the mount operation
     *   - "password_save" -> (u): a GPasswordSave
     *
     * The dialog will stay visible until clients call the Close() method, or
     * another dialog becomes visible.
     * Calling AskPassword again for the same id will have the effect to clear
     * the existing dialog and update it with a message indicating the previous
     * attempt went wrong.
     */
    AskPasswordAsync(params, invocation) {
        let [id, message, iconName, defaultUser, defaultDomain, flags] = params;

        if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_PASSWORD)) {
            this._dialog.reaskPassword();
            return;
        }

        this._closeDialog();

        this._dialog = new ShellMountPasswordDialog(message, this._createGIcon(iconName), flags);
        this._dialog.connect('response',
            (object, choice, password, remember) => {
                let details = {};
                let response;

                if (choice == -1) {
                    response = Gio.MountOperationResult.ABORTED;
                } else {
                    response = Gio.MountOperationResult.HANDLED;

                    let passSave = remember ? Gio.PasswordSave.PERMANENTLY : Gio.PasswordSave.NEVER;
                    details['password_save'] = GLib.Variant.new('u', passSave);
                    details['password'] = GLib.Variant.new('s', password);
                }

                this._clearCurrentRequest(response, details);
            });
        this._dialog.open();
    }

    /**
     * AskQuestion:
     * @id: an opaque ID identifying the object for which the operation is requested
     *      The ID must be unique in the context of the calling process.
     * @message: the message to display
     * @icon_name: the name of an icon to display
     * @choices: an array of choice strings
     * GetResponse:
     * @response: a GMountOperationResult
     * @response_details: a dictionary containing the response details as
     * entered by the user. The dictionary MAY contain the following properties:
     *   - "choice" -> (i): the chosen answer among the array of strings passed in
     *
     * The dialog will stay visible until clients call the Close() method, or
     * another dialog becomes visible.
     * Calling AskQuestion again for the same id will have the effect to clear
     * update the dialog with the new question.
     */
    AskQuestionAsync(params, invocation) {
        let [id, message, iconName, choices] = params;

        if (this._setCurrentRequest(invocation, id, ShellMountOperationType.ASK_QUESTION)) {
            this._dialog.update(message, choices);
            return;
        }

        this._closeDialog();

        this._dialog = new ShellMountQuestionDialog(this._createGIcon(iconName), message);
        this._dialog.connect('response', (object, choice) => {
            this._clearCurrentRequest(Gio.MountOperationResult.HANDLED,
                                      { choice: GLib.Variant.new('i', choice) });
        });

        this._dialog.update(message, choices);
        this._dialog.open();
    }

    /**
     * ShowProcesses:
     * @id: an opaque ID identifying the object for which the operation is requested
     *      The ID must be unique in the context of the calling process.
     * @message: the message to display
     * @icon_name: the name of an icon to display
     * @application_pids: the PIDs of the applications to display
     * @choices: an array of choice strings
     * @response: a GMountOperationResult
     * @response_details: a dictionary containing the response details as
     * entered by the user. The dictionary MAY contain the following properties:
     *   - "choice" -> (i): the chosen answer among the array of strings passed in
     *
     * The dialog will stay visible until clients call the Close() method, or
     * another dialog becomes visible.
     * Calling ShowProcesses again for the same id will have the effect to clear
     * the existing dialog and update it with the new message and the new list
     * of processes.
     */
    ShowProcessesAsync(params, invocation) {
        let [id, message, iconName, applicationPids, choices] = params;

        if (this._setCurrentRequest(invocation, id, ShellMountOperationType.SHOW_PROCESSES)) {
            this._dialog.update(message, applicationPids, choices);
            return;
        }

        this._closeDialog();

        this._dialog = new ShellProcessesDialog(this._createGIcon(iconName));
        this._dialog.connect('response', (object, choice) => {
            let response;
            let details = {};

            if (choice == -1) {
                response = Gio.MountOperationResult.ABORTED;
            } else {
                response = Gio.MountOperationResult.HANDLED;
                details['choice'] = GLib.Variant.new('i', choice);
            }

            this._clearCurrentRequest(response, details);
        });

        this._dialog.update(message, applicationPids, choices);
        this._dialog.open();
    }

    /**
     * Close:
     *
     * Closes a dialog previously opened by AskPassword, AskQuestion or ShowProcesses.
     * If no dialog is open, does nothing.
     */
    Close(params, invocation) {
        this._clearCurrentRequest(Gio.MountOperationResult.UNHANDLED, {});
        this._closeDialog();
    }
};
(uuay)checkBox.js2const { Clutter, Pango, St } = imports.gi;

var CheckBox = class CheckBox {
    constructor(label) {
        let container = new St.BoxLayout();
        this.actor = new St.Button({ style_class: 'check-box',
                                     child: container,
                                     button_mask: St.ButtonMask.ONE,
                                     toggle_mode: true,
                                     can_focus: true,
                                     x_fill: true,
                                     y_fill: true });

        this._box = new St.Bin();
        this._box.set_y_align(Clutter.ActorAlign.START);
        container.add_actor(this._box);

        this._label = new St.Label();
        this._label.clutter_text.set_line_wrap(true);
        this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
        container.add_actor(this._label);

        if (label)
            this.setLabel(label);
    }

    setLabel(label) {
        this._label.set_text(label);
    }

    getLabelActor() {
        return this._label;
    }
};
(uuay)switcherPopup.js9T// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, GLib, GObject, Meta, St } = imports.gi;
const Mainloop = imports.mainloop;

const Main = imports.ui.main;
const Tweener = imports.ui.tweener;

var POPUP_DELAY_TIMEOUT = 150; // milliseconds

var POPUP_SCROLL_TIME = 0.10; // seconds
var POPUP_FADE_OUT_TIME = 0.1; // seconds

var DISABLE_HOVER_TIMEOUT = 500; // milliseconds
var NO_MODS_TIMEOUT = 1500; // milliseconds

function mod(a, b) {
    return (a + b) % b;
}

function primaryModifier(mask) {
    if (mask == 0)
        return 0;

    let primary = 1;
    while (mask > 1) {
        mask >>= 1;
        primary <<= 1;
    }
    return primary;
}

var SwitcherPopup = GObject.registerClass(
class SwitcherPopup extends St.Widget {
    _init(items) {
        if (new.target === SwitcherPopup)
            throw new TypeError('Cannot instantiate abstract class ' + new.target.name);

        super._init({ style_class: 'switcher-popup',
                      reactive: true,
                      visible: false });

        this._switcherList = null;

        this._items = items || [];
        this._selectedIndex = 0;

        this.connect('destroy', this._onDestroy.bind(this));

        Main.uiGroup.add_actor(this);

        this._haveModal = false;
        this._modifierMask = 0;

        this._motionTimeoutId = 0;
        this._initialDelayTimeoutId = 0;
        this._noModsTimeoutId = 0;

        this.add_constraint(new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        }));

        // Initially disable hover so we ignore the enter-event if
        // the switcher appears underneath the current pointer location
        this._disableHover();
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let childBox = new Clutter.ActorBox();
        let primary = Main.layoutManager.primaryMonitor;

        let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT);
        let rightPadding = this.get_theme_node().get_padding(St.Side.RIGHT);
        let hPadding = leftPadding + rightPadding;

        // Allocate the switcherList
        // We select a size based on an icon size that does not overflow the screen
        let [childMinHeight, childNaturalHeight] = this._switcherList.get_preferred_height(primary.width - hPadding);
        let [childMinWidth, childNaturalWidth] = this._switcherList.get_preferred_width(childNaturalHeight);
        childBox.x1 = Math.max(primary.x + leftPadding, primary.x + Math.floor((primary.width - childNaturalWidth) / 2));
        childBox.x2 = Math.min(primary.x + primary.width - rightPadding, childBox.x1 + childNaturalWidth);
        childBox.y1 = primary.y + Math.floor((primary.height - childNaturalHeight) / 2);
        childBox.y2 = childBox.y1 + childNaturalHeight;
        this._switcherList.allocate(childBox, flags);
    }

    _initialSelection(backward, binding) {
        if (backward)
            this._select(this._items.length - 1);
        else if (this._items.length == 1)
            this._select(0);
        else
            this._select(1);
    }

    show(backward, binding, mask) {
        if (this._items.length == 0)
            return false;

        if (!Main.pushModal(this)) {
            // Probably someone else has a pointer grab, try again with keyboard only
            if (!Main.pushModal(this, { options: Meta.ModalOptions.POINTER_ALREADY_GRABBED }))
                return false;
        }
        this._haveModal = true;
        this._modifierMask = primaryModifier(mask);

        this.connect('key-press-event', this._keyPressEvent.bind(this));
        this.connect('key-release-event', this._keyReleaseEvent.bind(this));

        this.connect('button-press-event', this._clickedOutside.bind(this));
        this.connect('scroll-event', this._scrollEvent.bind(this));

        this.add_actor(this._switcherList);
        this._switcherList.connect('item-activated', this._itemActivated.bind(this));
        this._switcherList.connect('item-entered', this._itemEntered.bind(this));
        this._switcherList.connect('item-removed', this._itemRemoved.bind(this));

        // Need to force an allocation so we can figure out whether we
        // need to scroll when selecting
        this.opacity = 0;
        this.visible = true;
        this.get_allocation_box();

        this._initialSelection(backward, binding);

        // There's a race condition; if the user released Alt before
        // we got the grab, then we won't be notified. (See
        // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
        // details.) So we check now. (Have to do this after updating
        // selection.)
        if (this._modifierMask) {
            let [x, y, mods] = global.get_pointer();
            if (!(mods & this._modifierMask)) {
                this._finish(global.get_current_time());
                return false;
            }
        } else {
            this._resetNoModsTimeout();
        }

        // We delay showing the popup so that fast Alt+Tab users aren't
        // disturbed by the popup briefly flashing.
        this._initialDelayTimeoutId = Mainloop.timeout_add(POPUP_DELAY_TIMEOUT,
                                                           () => {
                                                               Main.osdWindowManager.hideAll();
                                                               this.opacity = 255;
                                                               this._initialDelayTimeoutId = 0;
                                                               return GLib.SOURCE_REMOVE;
                                                           });
        GLib.Source.set_name_by_id(this._initialDelayTimeoutId, '[gnome-shell] Main.osdWindow.cancel');
        return true;
    }

    _next() {
        return mod(this._selectedIndex + 1, this._items.length);
    }

    _previous() {
        return mod(this._selectedIndex - 1, this._items.length);
    }

    _keyPressHandler(keysym, action) {
        throw new Error('Not implemented');
    }

    _keyPressEvent(actor, event) {
        let keysym = event.get_key_symbol();
        let action = global.display.get_keybinding_action(event.get_key_code(), event.get_state());

        this._disableHover();

        if (this._keyPressHandler(keysym, action) != Clutter.EVENT_PROPAGATE)
            return Clutter.EVENT_STOP;

        // Note: pressing one of the below keys will destroy the popup only if
        // that key is not used by the active popup's keyboard shortcut
        if (keysym == Clutter.Escape || keysym == Clutter.Tab)
            this.fadeAndDestroy();

        return Clutter.EVENT_STOP;
    }

    _keyReleaseEvent(actor, event) {
        if (this._modifierMask) {
            let [x, y, mods] = global.get_pointer();
            let state = mods & this._modifierMask;

            if (state == 0)
                this._finish(event.get_time());
        } else {
            this._resetNoModsTimeout();
        }

        return Clutter.EVENT_STOP;
    }

    _clickedOutside(actor, event) {
        this.fadeAndDestroy();
        return Clutter.EVENT_PROPAGATE;
    }

    _scrollHandler(direction) {
        if (direction == Clutter.ScrollDirection.UP)
            this._select(this._previous());
        else if (direction == Clutter.ScrollDirection.DOWN)
            this._select(this._next());
    }

    _scrollEvent(actor, event) {
        this._scrollHandler(event.get_scroll_direction());
        return Clutter.EVENT_PROPAGATE;
    }

    _itemActivatedHandler(n) {
        this._select(n);
    }

    _itemActivated(switcher, n) {
        this._itemActivatedHandler(n);
        this._finish(global.get_current_time());
    }

    _itemEnteredHandler(n) {
        this._select(n);
    }

    _itemEntered(switcher, n) {
        if (!this.mouseActive)
            return;
        this._itemEnteredHandler(n);
    }

    _itemRemovedHandler(n) {
        if (this._items.length > 0) {
            let newIndex = Math.min(n, this._items.length - 1);
            this._select(newIndex);
        } else {
            this.fadeAndDestroy();
        }
    }

    _itemRemoved(switcher, n) {
        this._itemRemovedHandler(n);
    }

    _disableHover() {
        this.mouseActive = false;

        if (this._motionTimeoutId != 0)
            Mainloop.source_remove(this._motionTimeoutId);

        this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, this._mouseTimedOut.bind(this));
        GLib.Source.set_name_by_id(this._motionTimeoutId, '[gnome-shell] this._mouseTimedOut');
    }

    _mouseTimedOut() {
        this._motionTimeoutId = 0;
        this.mouseActive = true;
        return GLib.SOURCE_REMOVE;
    }

    _resetNoModsTimeout() {
        if (this._noModsTimeoutId != 0)
            Mainloop.source_remove(this._noModsTimeoutId);

        this._noModsTimeoutId = Mainloop.timeout_add(NO_MODS_TIMEOUT,
                                                     () => {
                                                         this._finish(global.get_current_time());
                                                         this._noModsTimeoutId = 0;
                                                         return GLib.SOURCE_REMOVE;
                                                     });
    }

    _popModal() {
        if (this._haveModal) {
            Main.popModal(this);
            this._haveModal = false;
        }
    }

    fadeAndDestroy() {
        this._popModal();
        if (this.visible) {
            Tweener.addTween(this,
                             { opacity: 0,
                               time: POPUP_FADE_OUT_TIME,
                               transition: 'easeOutQuad',
                               onComplete: () => {
                                   this.destroy();
                               }
                             });
        } else {
            this.destroy();
        }
    }

    _finish(timestamp) {
        this.fadeAndDestroy();
    }

    _onDestroy() {
        this._popModal();

        if (this._motionTimeoutId != 0)
            Mainloop.source_remove(this._motionTimeoutId);
        if (this._initialDelayTimeoutId != 0)
            Mainloop.source_remove(this._initialDelayTimeoutId);
        if (this._noModsTimeoutId != 0)
            Mainloop.source_remove(this._noModsTimeoutId);
    }

    _select(num) {
        this._selectedIndex = num;
        this._switcherList.highlight(num);
    }
});

var SwitcherButton = GObject.registerClass(
class SwitcherButton extends St.Button {
    _init(square) {
        super._init({ style_class: 'item-box',
                      reactive: true });

        this._square = square;
    }

    vfunc_get_preferred_width(forHeight) {
        if (this._square)
            return this.get_preferred_height(-1);
        else
            return super.vfunc_get_preferred_width(forHeight);
    }
});

var SwitcherList = GObject.registerClass({
    Signals: { 'item-activated': { param_types: [GObject.TYPE_INT] },
               'item-entered': { param_types: [GObject.TYPE_INT] },
               'item-removed': { param_types: [GObject.TYPE_INT] } },
}, class SwitcherList extends St.Widget {
    _init(squareItems) {
        super._init({ style_class: 'switcher-list' });

        this._list = new St.BoxLayout({ style_class: 'switcher-list-item-container',
                                        vertical: false,
                                        x_expand: true,
                                        y_expand: true });

        let layoutManager = this._list.get_layout_manager();

        this._list.spacing = 0;
        this._list.connect('style-changed', () => {
            this._list.spacing = this._list.get_theme_node().get_length('spacing');
        });

        this._scrollView = new St.ScrollView({ style_class: 'hfade',
                                               enable_mouse_scrolling: false });
        this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.NEVER);

        this._scrollView.add_actor(this._list);
        this.add_actor(this._scrollView);

        // Those arrows indicate whether scrolling in one direction is possible
        this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
                                               pseudo_class: 'highlighted' });
        this._leftArrow.connect('repaint', () => {
            drawArrow(this._leftArrow, St.Side.LEFT);
        });
        this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
                                                pseudo_class: 'highlighted' });
        this._rightArrow.connect('repaint', () => {
            drawArrow(this._rightArrow, St.Side.RIGHT);
        });

        this.add_actor(this._leftArrow);
        this.add_actor(this._rightArrow);

        this._items = [];
        this._highlighted = -1;
        this._squareItems = squareItems;
        this._scrollableRight = true;
        this._scrollableLeft = false;

        layoutManager.homogeneous = squareItems;
    }

    addItem(item, label) {
        let bbox = new SwitcherButton(this._squareItems);

        bbox.set_child(item);
        this._list.add_actor(bbox);

        let n = this._items.length;
        bbox.connect('clicked', () => { this._onItemClicked(n); });
        bbox.connect('motion-event', () => this._onItemEnter(n));

        bbox.label_actor = label;

        this._items.push(bbox);

        return bbox;
    }

    removeItem(index) {
        let item = this._items.splice(index, 1);
        item[0].destroy();
        this.emit('item-removed', index);
    }

    _onItemClicked(index) {
        this._itemActivated(index);
    }

    _onItemEnter(index) {
        // Avoid reentrancy
        if (index != this._currentItemEntered) {
            this._currentItemEntered = index;
            this._itemEntered(index);
        }
        return Clutter.EVENT_PROPAGATE;
    }

    highlight(index, justOutline) {
        if (this._items[this._highlighted]) {
            this._items[this._highlighted].remove_style_pseudo_class('outlined');
            this._items[this._highlighted].remove_style_pseudo_class('selected');
        }

        if (this._items[index]) {
            if (justOutline)
                this._items[index].add_style_pseudo_class('outlined');
            else
                this._items[index].add_style_pseudo_class('selected');
        }

        this._highlighted = index;

        let adjustment = this._scrollView.hscroll.adjustment;
        let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();
        let [absItemX, absItemY] = this._items[index].get_transformed_position();
        let [result, posX, posY] = this.transform_stage_point(absItemX, 0);
        let [containerWidth, containerHeight] = this.get_transformed_size();
        if (posX + this._items[index].get_width() > containerWidth)
            this._scrollToRight();
        else if (this._items[index].allocation.x1 - value < 0)
            this._scrollToLeft();

    }

    _scrollToLeft() {
        let adjustment = this._scrollView.hscroll.adjustment;
        let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();

        let item = this._items[this._highlighted];

        if (item.allocation.x1 < value)
            value = Math.min(0, item.allocation.x1);
        else if (item.allocation.x2 > value + pageSize)
            value = Math.max(upper, item.allocation.x2 - pageSize);

        this._scrollableRight = true;
        Tweener.addTween(adjustment,
                         { value: value,
                           time: POPUP_SCROLL_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                                if (this._highlighted == 0)
                                    this._scrollableLeft = false;
                                this.queue_relayout();
                           }
                          });
    }

    _scrollToRight() {
        let adjustment = this._scrollView.hscroll.adjustment;
        let [value, lower, upper, stepIncrement, pageIncrement, pageSize] = adjustment.get_values();

        let item = this._items[this._highlighted];

        if (item.allocation.x1 < value)
            value = Math.max(0, item.allocation.x1);
        else if (item.allocation.x2 > value + pageSize)
            value = Math.min(upper, item.allocation.x2 - pageSize);

        this._scrollableLeft = true;
        Tweener.addTween(adjustment,
                         { value: value,
                           time: POPUP_SCROLL_TIME,
                           transition: 'easeOutQuad',
                           onComplete: () => {
                                if (this._highlighted == this._items.length - 1)
                                    this._scrollableRight = false;
                                this.queue_relayout();
                            }
                          });
    }

    _itemActivated(n) {
        this.emit('item-activated', n);
    }

    _itemEntered(n) {
        this.emit('item-entered', n);
    }

    _maxChildWidth(forHeight) {
        let maxChildMin = 0;
        let maxChildNat = 0;

        for (let i = 0; i < this._items.length; i++) {
            let [childMin, childNat] = this._items[i].get_preferred_width(forHeight);
            maxChildMin = Math.max(childMin, maxChildMin);
            maxChildNat = Math.max(childNat, maxChildNat);

            if (this._squareItems) {
                let [childMin, childNat] = this._items[i].get_preferred_height(-1);
                maxChildMin = Math.max(childMin, maxChildMin);
                maxChildNat = Math.max(childNat, maxChildNat);
            }
        }

        return [maxChildMin, maxChildNat];
    }

    vfunc_get_preferred_width(forHeight) {
        let themeNode = this.get_theme_node();
        let [maxChildMin, ] = this._maxChildWidth(forHeight);
        let [minListWidth, ] = this._list.get_preferred_width(forHeight);

        return themeNode.adjust_preferred_width(maxChildMin, minListWidth);
    }

    vfunc_get_preferred_height(forWidth) {
        let maxChildMin = 0;
        let maxChildNat = 0;

        for (let i = 0; i < this._items.length; i++) {
            let [childMin, childNat] = this._items[i].get_preferred_height(-1);
            maxChildMin = Math.max(childMin, maxChildMin);
            maxChildNat = Math.max(childNat, maxChildNat);
        }

        if (this._squareItems) {
            let [childMin, childNat] = this._maxChildWidth(-1);
            maxChildMin = Math.max(childMin, maxChildMin);
            maxChildNat = maxChildMin;
        }

        let themeNode = this.get_theme_node();
        return themeNode.adjust_preferred_height(maxChildMin, maxChildNat);
    }

    vfunc_allocate(box, flags) {
        this.set_allocation(box, flags);

        let contentBox = this.get_theme_node().get_content_box(box);
        let width = contentBox.x2 - contentBox.x1;
        let height = contentBox.y2 - contentBox.y1;

        let leftPadding = this.get_theme_node().get_padding(St.Side.LEFT);
        let rightPadding = this.get_theme_node().get_padding(St.Side.RIGHT);

        let [, natScrollViewWidth] = this._scrollView.get_preferred_width(height);

        let childBox = new Clutter.ActorBox();
        let scrollable = natScrollViewWidth > width;

        this._scrollView.allocate(contentBox, flags);

        let arrowWidth = Math.floor(leftPadding / 3);
        let arrowHeight = arrowWidth * 2;
        childBox.x1 = leftPadding / 2;
        childBox.y1 = this.height / 2 - arrowWidth;
        childBox.x2 = childBox.x1 + arrowWidth;
        childBox.y2 = childBox.y1 + arrowHeight;
        this._leftArrow.allocate(childBox, flags);
        this._leftArrow.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;

        arrowWidth = Math.floor(rightPadding / 3);
        arrowHeight = arrowWidth * 2;
        childBox.x1 = this.width - arrowWidth - rightPadding / 2;
        childBox.y1 = this.height / 2 - arrowWidth;
        childBox.x2 = childBox.x1 + arrowWidth;
        childBox.y2 = childBox.y1 + arrowHeight;
        this._rightArrow.allocate(childBox, flags);
        this._rightArrow.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
    }
});

function drawArrow(area, side) {
    let themeNode = area.get_theme_node();
    let borderColor = themeNode.get_border_color(side);
    let bodyColor = themeNode.get_foreground_color();

    let [width, height] = area.get_surface_size ();
    let cr = area.get_context();

    cr.setLineWidth(1.0);
    Clutter.cairo_set_source_color(cr, borderColor);

    switch (side) {
    case St.Side.TOP:
        cr.moveTo(0, height);
        cr.lineTo(Math.floor(width * 0.5), 0);
        cr.lineTo(width, height);
        break;

    case St.Side.BOTTOM:
        cr.moveTo(width, 0);
        cr.lineTo(Math.floor(width * 0.5), height);
        cr.lineTo(0, 0);
        break;

    case St.Side.LEFT:
        cr.moveTo(width, height);
        cr.lineTo(0, Math.floor(height * 0.5));
        cr.lineTo(width, 0);
        break;

    case St.Side.RIGHT:
        cr.moveTo(0, 0);
        cr.lineTo(width, Math.floor(height * 0.5));
        cr.lineTo(0, height);
        break;
    }

    cr.strokePreserve();

    Clutter.cairo_set_source_color(cr, bodyColor);
    cr.fill();
    cr.$dispose();
}

(uuay)appDisplay.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;
const Signals = imports.signals;
const Mainloop = imports.mainloop;

const AppFavorites = imports.ui.appFavorites;
const BoxPointer = imports.ui.boxpointer;
const DND = imports.ui.dnd;
const GrabHelper = imports.ui.grabHelper;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const PageIndicators = imports.ui.pageIndicators;
const PopupMenu = imports.ui.popupMenu;
const Tweener = imports.ui.tweener;
const Search = imports.ui.search;
const Params = imports.misc.params;
const Util = imports.misc.util;
const SystemActions = imports.misc.systemActions;

const { loadInterfaceXML } = imports.misc.fileUtils;

var MAX_APPLICATION_WORK_MILLIS = 75;
var MENU_POPUP_TIMEOUT = 600;
var MAX_COLUMNS = 6;
var MIN_COLUMNS = 4;
var MIN_ROWS = 4;

var INACTIVE_GRID_OPACITY = 77;
// This time needs to be less than IconGrid.EXTRA_SPACE_ANIMATION_TIME
// to not clash with other animations
var INACTIVE_GRID_OPACITY_ANIMATION_TIME = 0.24;
var FOLDER_SUBICON_FRACTION = .4;

var MIN_FREQUENT_APPS_COUNT = 3;

var INDICATORS_BASE_TIME = 0.25;
var INDICATORS_ANIMATION_DELAY = 0.125;
var INDICATORS_ANIMATION_MAX_TIME = 0.75;

var VIEWS_SWITCH_TIME = 0.4;
var VIEWS_SWITCH_ANIMATION_DELAY = 0.1;

// Follow iconGrid animations approach and divide by 2 to animate out to
// not annoy the user when the user wants to quit appDisplay.
// Also, make sure we don't exceed iconGrid animation total time or
// views switch time.
var INDICATORS_BASE_TIME_OUT = 0.125;
var INDICATORS_ANIMATION_DELAY_OUT = 0.0625;
var INDICATORS_ANIMATION_MAX_TIME_OUT =
    Math.min (VIEWS_SWITCH_TIME,
              IconGrid.ANIMATION_TIME_OUT + IconGrid.ANIMATION_MAX_DELAY_OUT_FOR_ITEM);

var PAGE_SWITCH_TIME = 0.3;

const SWITCHEROO_BUS_NAME = 'net.hadess.SwitcherooControl';
const SWITCHEROO_OBJECT_PATH = '/net/hadess/SwitcherooControl';

const SwitcherooProxyInterface = loadInterfaceXML('net.hadess.SwitcherooControl');
const SwitcherooProxy = Gio.DBusProxy.makeProxyWrapper(SwitcherooProxyInterface);
let discreteGpuAvailable = false;

function _getCategories(info) {
    let categoriesStr = info.get_categories();
    if (!categoriesStr)
        return [];
    return categoriesStr.split(';');
}

function _listsIntersect(a, b) {
    for (let itemA of a)
        if (b.indexOf(itemA) >= 0)
            return true;
    return false;
}

function _getFolderName(folder) {
    let name = folder.get_string('name');

    if (folder.get_boolean('translate')) {
        let translated = Shell.util_get_translated_folder_name(name);
        if (translated !== null)
            return translated;
    }

    return name;
}

function clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
}

class BaseAppView {
    constructor(params, gridParams) {
        if (new.target === BaseAppView)
            throw new TypeError('Cannot instantiate abstract class ' + new.target.name);

        gridParams = Params.parse(gridParams, { xAlign: St.Align.MIDDLE,
                                                columnLimit: MAX_COLUMNS,
                                                minRows: MIN_ROWS,
                                                minColumns: MIN_COLUMNS,
                                                fillParent: false,
                                                padWithSpacing: true });
        params = Params.parse(params, { usePagination: false });

        if(params.usePagination)
            this._grid = new IconGrid.PaginatedIconGrid(gridParams);
        else
            this._grid = new IconGrid.IconGrid(gridParams);

        this._grid.connect('child-focused', (grid, actor) => {
            this._childFocused(actor);
        });
        // Standard hack for ClutterBinLayout
        this._grid.x_expand = true;

        this._items = {};
        this._allItems = [];
    }

    _childFocused(actor) {
        // Nothing by default
    }

    removeAll() {
        this._grid.destroyAll();
        this._items = {};
        this._allItems = [];
    }

    _redisplay() {
        this.removeAll();
        this._loadApps();
    }

    getAllItems() {
        return this._allItems;
    }

    hasItem(id) {
        return this._items[id] !== undefined;
    }

    addItem(icon) {
        let id = icon.id;
	if (this.hasItem(id))
            throw new Error(`icon with id ${id} already added to view`)

        this._allItems.push(icon);
        this._items[id] = icon;
    }

    _compareItems(a, b) {
        return a.name.localeCompare(b.name);
    }

    loadGrid() {
        this._allItems.sort(this._compareItems);
        this._allItems.forEach(item => { this._grid.addItem(item); });
        this.emit('view-loaded');
    }

    _selectAppInternal(id) {
        if (this._items[id])
            this._items[id].actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
        else
            log('No such application ' + id);
    }

    selectApp(id) {
        if (this._items[id] && this._items[id].actor.mapped) {
            this._selectAppInternal(id);
        } else if (this._items[id]) {
            // Need to wait until the view is mapped
            let signalId = this._items[id].actor.connect('notify::mapped',
                actor => {
                    if (actor.mapped) {
                        actor.disconnect(signalId);
                        this._selectAppInternal(id);
                    }
                });
        } else {
            // Need to wait until the view is built
            let signalId = this.connect('view-loaded', () => {
                this.disconnect(signalId);
                this.selectApp(id);
            });
        }
    }

    _doSpringAnimation(animationDirection) {
        this._grid.opacity = 255;
        this._grid.animateSpring(animationDirection,
                                 Main.overview.getShowAppsButton());
    }

    animate(animationDirection, onComplete) {
        if (onComplete) {
            let animationDoneId = this._grid.connect('animation-done', () => {
                this._grid.disconnect(animationDoneId);
                onComplete();
            });
        }

        if (animationDirection == IconGrid.AnimationDirection.IN) {
            let id = this._grid.connect('paint', () => {
                this._grid.disconnect(id);
                this._doSpringAnimation(animationDirection);
            });
        } else {
            this._doSpringAnimation(animationDirection);
        }
    }

    animateSwitch(animationDirection) {
        Tweener.removeTweens(this.actor);
        Tweener.removeTweens(this._grid);

        let params = { time: VIEWS_SWITCH_TIME,
                       transition: 'easeOutQuad' };
        if (animationDirection == IconGrid.AnimationDirection.IN) {
            this.actor.show();
            params.opacity = 255;
            params.delay = VIEWS_SWITCH_ANIMATION_DELAY;
        } else {
            params.opacity = 0;
            params.delay = 0;
            params.onComplete = () => { this.actor.hide(); };
        }

        Tweener.addTween(this._grid, params);
    }
};
Signals.addSignalMethods(BaseAppView.prototype);

var AllView = class AllView extends BaseAppView {
    constructor() {
        super({ usePagination: true }, null);
        this._scrollView = new St.ScrollView({ style_class: 'all-apps',
                                               x_expand: true,
                                               y_expand: true,
                                               x_fill: true,
                                               y_fill: false,
                                               reactive: true,
                                               y_align: St.Align.START });
        this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
                                     x_expand:true, y_expand:true });
        this.actor.add_actor(this._scrollView);

        this._scrollView.set_policy(St.PolicyType.NEVER,
                                    St.PolicyType.EXTERNAL);
        this._adjustment = this._scrollView.vscroll.adjustment;

        this._pageIndicators = new PageIndicators.AnimatedPageIndicators();
        this._pageIndicators.connect('page-activated',
            (indicators, pageIndex) => {
                this.goToPage(pageIndex);
            });
        this._pageIndicators.connect('scroll-event', this._onScroll.bind(this));
        this.actor.add_actor(this._pageIndicators);

        this.folderIcons = [];

        this._stack = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        let box = new St.BoxLayout({ vertical: true });

        this._grid.currentPage = 0;
        this._stack.add_actor(this._grid);
        this._eventBlocker = new St.Widget({ x_expand: true, y_expand: true });
        this._stack.add_actor(this._eventBlocker);

        box.add_actor(this._stack);
        this._scrollView.add_actor(box);

        this._scrollView.connect('scroll-event', this._onScroll.bind(this));

        let panAction = new Clutter.PanAction({ interpolate: false });
        panAction.connect('pan', this._onPan.bind(this));
        panAction.connect('gesture-cancel', this._onPanEnd.bind(this));
        panAction.connect('gesture-end', this._onPanEnd.bind(this));
        this._panAction = panAction;
        this._scrollView.add_action(panAction);
        this._panning = false;
        this._clickAction = new Clutter.ClickAction();
        this._clickAction.connect('clicked', () => {
            if (!this._currentPopup)
                return;

            let [x, y] = this._clickAction.get_coords();
            let actor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
            if (!this._currentPopup.actor.contains(actor))
                this._currentPopup.popdown();
        });
        this._eventBlocker.add_action(this._clickAction);

        this._displayingPopup = false;
        this._currentPopupDestroyId = 0;

        this._availWidth = 0;
        this._availHeight = 0;

        Main.overview.connect('hidden', () => { this.goToPage(0); });
        this._grid.connect('space-opened', () => {
            let fadeEffect = this._scrollView.get_effect('fade');
            if (fadeEffect)
                fadeEffect.enabled = false;

            this.emit('space-ready');
        });
        this._grid.connect('space-closed', () => {
            this._displayingPopup = false;
        });

        this.actor.connect('notify::mapped', () => {
            if (this.actor.mapped) {
                this._keyPressEventId =
                    global.stage.connect('key-press-event',
                                         this._onKeyPressEvent.bind(this));
            } else {
                if (this._keyPressEventId)
                    global.stage.disconnect(this._keyPressEventId);
                this._keyPressEventId = 0;
            }
        });

        this._redisplayWorkId = Main.initializeDeferredWork(this.actor, this._redisplay.bind(this));

        Shell.AppSystem.get_default().connect('installed-changed', () => {
            Main.queueDeferredWork(this._redisplayWorkId);
        });
        this._folderSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
        this._folderSettings.connect('changed::folder-children', () => {
            Main.queueDeferredWork(this._redisplayWorkId);
        });
    }

    removeAll() {
        this.folderIcons = [];
        super.removeAll();
    }

    _redisplay() {
        let openFolderId = null;
        if (this._displayingPopup && this._currentPopup)
            openFolderId = this._currentPopup._source.id;

        super._redisplay();

        if (openFolderId) {
            let [folderToReopen] = this.folderIcons.filter(folder => folder.id == openFolderId);

            if (folderToReopen)
                folderToReopen.open();
        }
    }

    _itemNameChanged(item) {
        // If an item's name changed, we can pluck it out of where it's
        // supposed to be and reinsert it where it's sorted.
        let oldIdx = this._allItems.indexOf(item);
        this._allItems.splice(oldIdx, 1);
        let newIdx = Util.insertSorted(this._allItems, item, this._compareItems);

        this._grid.removeItem(item);
        this._grid.addItem(item, newIdx);
    }

    _refilterApps() {
        this._allItems.forEach(icon => {
            if (icon instanceof AppIcon)
                icon.actor.visible = true;
        });

        this.folderIcons.forEach(folder => {
            let folderApps = folder.getAppIds();
            folderApps.forEach(appId => {
                let appIcon = this._items[appId];
                appIcon.actor.visible = false;
            });
        });
    }

    getAppInfos() {
        return this._appInfoList;
    }

    _loadApps() {
        this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
            try {
                let id = appInfo.get_id(); // catch invalid file encodings
            } catch(e) {
                return false;
            }
            return appInfo.should_show();
        });

        let apps = this._appInfoList.map(app => app.get_id());

        let appSys = Shell.AppSystem.get_default();

        let folders = this._folderSettings.get_strv('folder-children');
        folders.forEach(id => {
            if (this.hasItem(id))
                return;
            let path = this._folderSettings.path + 'folders/' + id + '/';
            let icon = new FolderIcon(id, path, this);
            icon.connect('name-changed', this._itemNameChanged.bind(this));
            icon.connect('apps-changed', this._refilterApps.bind(this));
            this.addItem(icon);
            this.folderIcons.push(icon);
        });

        // Allow dragging of the icon only if the Dash would accept a drop to
        // change favorite-apps. There are no other possible drop targets from
        // the app picker, so there's no other need for a drag to start,
        // at least on single-monitor setups.
        // This also disables drag-to-launch on multi-monitor setups,
        // but we hope that is not used much.
        let favoritesWritable = global.settings.is_writable('favorite-apps');

        apps.forEach(appId => {
            let app = appSys.lookup_app(appId);

            let icon = new AppIcon(app,
                                   { isDraggable: favoritesWritable });
            this.addItem(icon);
        });

        this.loadGrid();
        this._refilterApps();
    }

    // Overriden from BaseAppView
    animate(animationDirection, onComplete) {
        this._scrollView.reactive = false;
        let completionFunc = () => {
            this._scrollView.reactive = true;
            if (onComplete)
                onComplete();
        };

        if (animationDirection == IconGrid.AnimationDirection.OUT &&
            this._displayingPopup && this._currentPopup) {
            this._currentPopup.popdown();
            let spaceClosedId = this._grid.connect('space-closed', () => {
                this._grid.disconnect(spaceClosedId);
                super.animate(animationDirection, completionFunc);
            });
        } else {
            super.animate(animationDirection, completionFunc);
            if (animationDirection == IconGrid.AnimationDirection.OUT)
                this._pageIndicators.animateIndicators(animationDirection);
        }
    }

    animateSwitch(animationDirection) {
        super.animateSwitch(animationDirection);

        if (this._currentPopup && this._displayingPopup &&
            animationDirection == IconGrid.AnimationDirection.OUT)
            Tweener.addTween(this._currentPopup.actor,
                             { time: VIEWS_SWITCH_TIME,
                               transition: 'easeOutQuad',
                               opacity: 0,
                               onComplete() {
                                  this.opacity = 255;
                               } });

        if (animationDirection == IconGrid.AnimationDirection.OUT)
            this._pageIndicators.animateIndicators(animationDirection);
    }

    getCurrentPageY() {
        return this._grid.getPageY(this._grid.currentPage);
    }

    goToPage(pageNumber) {
        pageNumber = clamp(pageNumber, 0, this._grid.nPages() - 1);

        if (this._grid.currentPage == pageNumber && this._displayingPopup && this._currentPopup)
            return;
        if (this._displayingPopup && this._currentPopup)
            this._currentPopup.popdown();

        let velocity;
        if (!this._panning)
            velocity = 0;
        else
            velocity = Math.abs(this._panAction.get_velocity(0)[2]);
        // Tween the change between pages.
        // If velocity is not specified (i.e. scrolling with mouse wheel),
        // use the same speed regardless of original position
        // if velocity is specified, it's in pixels per milliseconds
        let diffToPage = this._diffToPage(pageNumber);
        let childBox = this._scrollView.get_allocation_box();
        let totalHeight = childBox.y2 - childBox.y1;
        let time;
        // Only take the velocity into account on page changes, otherwise
        // return smoothly to the current page using the default velocity
        if (this._grid.currentPage != pageNumber) {
            let minVelocity = totalHeight / (PAGE_SWITCH_TIME * 1000);
            velocity = Math.max(minVelocity, velocity);
            time = (diffToPage / velocity) / 1000;
        } else {
            time = PAGE_SWITCH_TIME * diffToPage / totalHeight;
        }
        // When changing more than one page, make sure to not take
        // longer than PAGE_SWITCH_TIME
        time = Math.min(time, PAGE_SWITCH_TIME);

        this._grid.currentPage = pageNumber;
        Tweener.addTween(this._adjustment,
                         { value: this._grid.getPageY(this._grid.currentPage),
                           time: time,
                           transition: 'easeOutQuad' });
        this._pageIndicators.setCurrentPage(pageNumber);
    }

    _diffToPage(pageNumber) {
        let currentScrollPosition = this._adjustment.value;
        return Math.abs(currentScrollPosition - this._grid.getPageY(pageNumber));
    }

    openSpaceForPopup(item, side, nRows) {
        this._updateIconOpacities(true);
        this._displayingPopup = true;
        this._grid.openExtraSpace(item, side, nRows);
    }

    _closeSpaceForPopup() {
        this._updateIconOpacities(false);

        let fadeEffect = this._scrollView.get_effect('fade');
        if (fadeEffect)
            fadeEffect.enabled = true;

        this._grid.closeExtraSpace();
    }

    _onScroll(actor, event) {
        if (this._displayingPopup || !this._scrollView.reactive)
            return Clutter.EVENT_STOP;

        let direction = event.get_scroll_direction();
        if (direction == Clutter.ScrollDirection.UP)
            this.goToPage(this._grid.currentPage - 1);
        else if (direction == Clutter.ScrollDirection.DOWN)
            this.goToPage(this._grid.currentPage + 1);

        return Clutter.EVENT_STOP;
    }

    _onPan(action) {
        if (this._displayingPopup)
            return false;
        this._panning = true;
        this._clickAction.release();
        let [dist, dx, dy] = action.get_motion_delta(0);
        let adjustment = this._adjustment;
        adjustment.value -= (dy / this._scrollView.height) * adjustment.page_size;
        return false;
    }

    _onPanEnd(action) {
         if (this._displayingPopup)
            return;

        let pageHeight = this._grid.getPageHeight();

        // Calculate the scroll value we'd be at, which is our current
        // scroll plus any velocity the user had when they released
        // their finger.

        let velocity = -action.get_velocity(0)[2];
        let endPanValue = this._adjustment.value + velocity;

        let closestPage = Math.round(endPanValue / pageHeight);
        this.goToPage(closestPage);

        this._panning = false;
    }

    _onKeyPressEvent(actor, event) {
        if (this._displayingPopup)
            return Clutter.EVENT_STOP;

        if (event.get_key_symbol() == Clutter.Page_Up) {
            this.goToPage(this._grid.currentPage - 1);
            return Clutter.EVENT_STOP;
        } else if (event.get_key_symbol() == Clutter.Page_Down) {
            this.goToPage(this._grid.currentPage + 1);
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    addFolderPopup(popup) {
        this._stack.add_actor(popup.actor);
        popup.connect('open-state-changed', (popup, isOpen) => {
            this._eventBlocker.reactive = isOpen;

            if (this._currentPopup) {
                this._currentPopup.actor.disconnect(this._currentPopupDestroyId);
                this._currentPopupDestroyId = 0;
            }

            this._currentPopup = null;

            if (isOpen) {
                this._currentPopup = popup;
                this._currentPopupDestroyId = popup.actor.connect('destroy', () => {
                    this._currentPopup = null;
                    this._currentPopupDestroyId = 0;
                    this._eventBlocker.reactive = false;
                });
            }
            this._updateIconOpacities(isOpen);
            if(!isOpen)
                this._closeSpaceForPopup();
        });
    }

    _childFocused(icon) {
        let itemPage = this._grid.getItemPage(icon);
        this.goToPage(itemPage);
    }

    _updateIconOpacities(folderOpen) {
        for (let id in this._items) {
            let params, opacity;
            if (folderOpen && !this._items[id].actor.checked)
                opacity =  INACTIVE_GRID_OPACITY;
            else
                opacity = 255;
            params = { opacity: opacity,
                       time: INACTIVE_GRID_OPACITY_ANIMATION_TIME,
                       transition: 'easeOutQuad' };
            Tweener.addTween(this._items[id].actor, params);
        }
    }

    // Called before allocation to calculate dynamic spacing
    adaptToSize(width, height) {
        let box = new Clutter.ActorBox();
        box.x1 = 0;
        box.x2 = width;
        box.y1 = 0;
        box.y2 = height;
        box = this.actor.get_theme_node().get_content_box(box);
        box = this._scrollView.get_theme_node().get_content_box(box);
        box = this._grid.get_theme_node().get_content_box(box);
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;
        let oldNPages = this._grid.nPages();

        this._grid.adaptToSize(availWidth, availHeight);

        let fadeOffset = Math.min(this._grid.topPadding,
                                  this._grid.bottomPadding);
        this._scrollView.update_fade_effect(fadeOffset, 0);
        if (fadeOffset > 0)
            this._scrollView.get_effect('fade').fade_edges = true;

        if (this._availWidth != availWidth || this._availHeight != availHeight || oldNPages != this._grid.nPages()) {
            this._adjustment.value = 0;
            this._grid.currentPage = 0;
            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this._pageIndicators.setNPages(this._grid.nPages());
                this._pageIndicators.setCurrentPage(0);
            });
        }

        this._availWidth = availWidth;
        this._availHeight = availHeight;
        // Update folder views
        for (let i = 0; i < this.folderIcons.length; i++)
            this.folderIcons[i].adaptToSize(availWidth, availHeight);
    }
};
Signals.addSignalMethods(AllView.prototype);

var FrequentView = class FrequentView extends BaseAppView {
    constructor() {
        super(null, { fillParent: true });

        this.actor = new St.Widget({ style_class: 'frequent-apps',
                                     layout_manager: new Clutter.BinLayout(),
                                     x_expand: true, y_expand: true });

        this._noFrequentAppsLabel = new St.Label({ text: _("Frequently used applications will appear here"),
                                                   style_class: 'no-frequent-applications-label',
                                                   x_align: Clutter.ActorAlign.CENTER,
                                                   x_expand: true,
                                                   y_align: Clutter.ActorAlign.CENTER,
                                                   y_expand: true });

        this._grid.y_expand = true;

        this.actor.add_actor(this._grid);
        this.actor.add_actor(this._noFrequentAppsLabel);
        this._noFrequentAppsLabel.hide();

        this._usage = Shell.AppUsage.get_default();

        this.actor.connect('notify::mapped', () => {
            if (this.actor.mapped)
                this._redisplay();
        });
    }

    hasUsefulData() {
        return this._usage.get_most_used().length >= MIN_FREQUENT_APPS_COUNT;
    }

    _loadApps() {
        let mostUsed = this._usage.get_most_used();
        let hasUsefulData = this.hasUsefulData();
        this._noFrequentAppsLabel.visible = !hasUsefulData;
        if(!hasUsefulData)
            return;

        // Allow dragging of the icon only if the Dash would accept a drop to
        // change favorite-apps. There are no other possible drop targets from
        // the app picker, so there's no other need for a drag to start,
        // at least on single-monitor setups.
        // This also disables drag-to-launch on multi-monitor setups,
        // but we hope that is not used much.
        let favoritesWritable = global.settings.is_writable('favorite-apps');

        for (let i = 0; i < mostUsed.length; i++) {
            if (!mostUsed[i].get_app_info().should_show())
                continue;
            let appIcon = new AppIcon(mostUsed[i],
                                      { isDraggable: favoritesWritable });
            this._grid.addItem(appIcon, -1);
        }
    }

    // Called before allocation to calculate dynamic spacing
    adaptToSize(width, height) {
        let box = new Clutter.ActorBox();
        box.x1 = box.y1 = 0;
        box.x2 = width;
        box.y2 = height;
        box = this.actor.get_theme_node().get_content_box(box);
        box = this._grid.get_theme_node().get_content_box(box);
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;
        this._grid.adaptToSize(availWidth, availHeight);
    }
};

var Views = {
    FREQUENT: 0,
    ALL: 1
};

var ControlsBoxLayout = GObject.registerClass(
class ControlsBoxLayout extends Clutter.BoxLayout {
    /**
     * Override the BoxLayout behavior to use the maximum preferred width of all
     * buttons for each child
     */
    vfunc_get_preferred_width(container, forHeight) {
        let maxMinWidth = 0;
        let maxNaturalWidth = 0;
        for (let child = container.get_first_child();
             child;
             child = child.get_next_sibling()) {
             let [minWidth, natWidth] = child.get_preferred_width(forHeight);
             maxMinWidth = Math.max(maxMinWidth, minWidth);
             maxNaturalWidth = Math.max(maxNaturalWidth, natWidth);
        }
        let childrenCount = container.get_n_children();
        let totalSpacing = this.spacing * (childrenCount - 1);
        return [maxMinWidth * childrenCount + totalSpacing,
                maxNaturalWidth * childrenCount + totalSpacing];
    }
});

var ViewStackLayout = GObject.registerClass({
    Signals: { 'allocated-size-changed': { param_types: [GObject.TYPE_INT,
                                                         GObject.TYPE_INT] } },
}, class ViewStackLayout extends Clutter.BinLayout {
    vfunc_allocate(actor, box, flags) {
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;
        // Prepare children of all views for the upcoming allocation, calculate all
        // the needed values to adapt available size
        this.emit('allocated-size-changed', availWidth, availHeight);
        super.vfunc_allocate(actor, box, flags);
    }
});

var AppDisplay = class AppDisplay {
    constructor() {
        this._privacySettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.privacy' });
        this._privacySettings.connect('changed::remember-app-usage',
                                      this._updateFrequentVisibility.bind(this));

        this._views = [];

        let view, button;
        view = new FrequentView();
        button = new St.Button({ label: _("Frequent"),
                                 style_class: 'app-view-control button',
                                 can_focus: true,
                                 x_expand: true });
        this._views[Views.FREQUENT] = { 'view': view, 'control': button };

        view = new AllView();
        button = new St.Button({ label: _("All"),
                                 style_class: 'app-view-control button',
                                 can_focus: true,
                                 x_expand: true });
        this._views[Views.ALL] = { 'view': view, 'control': button };

        this.actor = new St.BoxLayout ({ style_class: 'app-display',
                                         x_expand: true, y_expand: true,
                                         vertical: true });
        this._viewStackLayout = new ViewStackLayout();
        this._viewStack = new St.Widget({ x_expand: true, y_expand: true,
                                          layout_manager: this._viewStackLayout });
        this._viewStackLayout.connect('allocated-size-changed', this._onAllocatedSizeChanged.bind(this));
        this.actor.add_actor(this._viewStack);
        let layout = new ControlsBoxLayout({ homogeneous: true });
        this._controls = new St.Widget({ style_class: 'app-view-controls',
                                         layout_manager: layout });
        this._controls.connect('notify::mapped', () => {
            // controls are faded either with their parent or
            // explicitly in animate(); we can't know how they'll be
            // shown next, so make sure to restore their opacity
            // when they are hidden
            if (this._controls.mapped)
              return;

            Tweener.removeTweens(this._controls);
            this._controls.opacity = 255;
        });

        layout.hookup_style(this._controls);
        this.actor.add_actor(new St.Bin({ child: this._controls }));

        for (let i = 0; i < this._views.length; i++) {
            this._viewStack.add_actor(this._views[i].view.actor);
            this._controls.add_actor(this._views[i].control);

            let viewIndex = i;
            this._views[i].control.connect('clicked', actor => {
                this._showView(viewIndex);
                global.settings.set_uint('app-picker-view', viewIndex);
            });
        }
        let initialView = Math.min(global.settings.get_uint('app-picker-view'),
                                   this._views.length - 1);
        let frequentUseful = this._views[Views.FREQUENT].view.hasUsefulData();
        if (initialView == Views.FREQUENT && !frequentUseful)
            initialView = Views.ALL;
        this._showView(initialView);
        this._updateFrequentVisibility();

        Gio.DBus.system.watch_name(SWITCHEROO_BUS_NAME,
                                   Gio.BusNameWatcherFlags.NONE,
                                   this._switcherooProxyAppeared.bind(this),
                                   () => {
                                       this._switcherooProxy = null;
                                       this._updateDiscreteGpuAvailable();
                                   });
    }

    _updateDiscreteGpuAvailable() {
        if (!this._switcherooProxy)
            discreteGpuAvailable = false;
        else
            discreteGpuAvailable = this._switcherooProxy.HasDualGpu;
    }

    _switcherooProxyAppeared() {
        this._switcherooProxy = new SwitcherooProxy(Gio.DBus.system, SWITCHEROO_BUS_NAME, SWITCHEROO_OBJECT_PATH,
            (proxy, error) => {
                if (error) {
                    log(error.message);
                    return;
                }
                this._updateDiscreteGpuAvailable();
            });
    }

    animate(animationDirection, onComplete) {
        let currentView = this._views.filter(v => v.control.has_style_pseudo_class('checked')).pop().view;

        // Animate controls opacity using iconGrid animation time, since
        // it will be the time the AllView or FrequentView takes to show
        // it entirely.
        let finalOpacity;
        if (animationDirection == IconGrid.AnimationDirection.IN) {
            this._controls.opacity = 0;
            finalOpacity = 255;
        } else {
            finalOpacity = 0
        }

        Tweener.addTween(this._controls,
                         { time: IconGrid.ANIMATION_TIME_IN,
                           transition: 'easeInOutQuad',
                           opacity: finalOpacity,
                          });

        currentView.animate(animationDirection, onComplete);
    }

    _showView(activeIndex) {
        for (let i = 0; i < this._views.length; i++) {
            if (i == activeIndex)
                this._views[i].control.add_style_pseudo_class('checked');
            else
                this._views[i].control.remove_style_pseudo_class('checked');

            let animationDirection = i == activeIndex ? IconGrid.AnimationDirection.IN :
                                                        IconGrid.AnimationDirection.OUT;
            this._views[i].view.animateSwitch(animationDirection);
        }
    }

    _updateFrequentVisibility() {
        let enabled = this._privacySettings.get_boolean('remember-app-usage');
        this._views[Views.FREQUENT].control.visible = enabled;

        let visibleViews = this._views.filter(v => v.control.visible);
        this._controls.visible = visibleViews.length > 1;

        if (!enabled && this._views[Views.FREQUENT].view.actor.visible)
            this._showView(Views.ALL);
    }

    selectApp(id) {
        this._showView(Views.ALL);
        this._views[Views.ALL].view.selectApp(id);
    }

    _onAllocatedSizeChanged(actor, width, height) {
        let box = new Clutter.ActorBox();
        box.x1 = box.y1 =0;
        box.x2 = width;
        box.y2 = height;
        box = this._viewStack.get_theme_node().get_content_box(box);
        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;
        for (let i = 0; i < this._views.length; i++)
            this._views[i].view.adaptToSize(availWidth, availHeight);
    }
};

var AppSearchProvider = class AppSearchProvider {
    constructor() {
        this._appSys = Shell.AppSystem.get_default();
        this.id = 'applications';
        this.isRemoteProvider = false;
        this.canLaunchSearch = false;

        this._systemActions = new SystemActions.getDefault();
    }

    getResultMetas(apps, callback) {
        let metas = [];
        for (let id of apps) {
            if (id.endsWith('.desktop')) {
                let app = this._appSys.lookup_app(id);

                metas.push({ 'id': app.get_id(),
                             'name': app.get_name(),
                             'createIcon'(size) {
                                 return app.create_icon_texture(size);
                           }
                });
            } else {
                let name = this._systemActions.getName(id);
                let iconName = this._systemActions.getIconName(id);

                let createIcon = size => new St.Icon({ icon_name: iconName,
                                                       width: size,
                                                       height: size,
                                                       style_class: 'system-action-icon' });

                metas.push({ id, name, createIcon });
            }
        }

        callback(metas);
    }

    filterResults(results, maxNumber) {
        return results.slice(0, maxNumber);
    }

    getInitialResultSet(terms, callback, cancellable) {
        let query = terms.join(' ');
        let groups = Shell.AppSystem.search(query);
        let usage = Shell.AppUsage.get_default();
        let results = [];
        groups.forEach(group => {
            group = group.filter(appID => {
                const app = this._appSys.lookup_app(appID);
                return app && app.app_info.should_show();
            });
            results = results.concat(group.sort(
                (a, b) => usage.compare(a, b)
            ));
        });

        results = results.concat(this._systemActions.getMatchingActions(terms));

        callback(results);
    }

    getSubsearchResultSet(previousResults, terms, callback, cancellable) {
        this.getInitialResultSet(terms, callback, cancellable);
    }

    createResultObject(resultMeta) {
        if (resultMeta.id.endsWith('.desktop'))
            return new AppIcon(this._appSys.lookup_app(resultMeta['id']));
        else
            return new SystemActionIcon(this, resultMeta);
    }
};

var FolderView = class FolderView extends BaseAppView {
    constructor() {
        super(null, null);
        // If it not expand, the parent doesn't take into account its preferred_width when allocating
        // the second time it allocates, so we apply the "Standard hack for ClutterBinLayout"
        this._grid.x_expand = true;

        this.actor = new St.ScrollView({ overlay_scrollbars: true });
        this.actor.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        let scrollableContainer = new St.BoxLayout({ vertical: true, reactive: true });
        scrollableContainer.add_actor(this._grid);
        this.actor.add_actor(scrollableContainer);

        let action = new Clutter.PanAction({ interpolate: true });
        action.connect('pan', this._onPan.bind(this));
        this.actor.add_action(action);
    }

    _childFocused(actor) {
        Util.ensureActorVisibleInScrollView(this.actor, actor);
    }

    // Overriden from BaseAppView
    animate(animationDirection) {
        this._grid.animatePulse(animationDirection);
    }

    createFolderIcon(size) {
        let layout = new Clutter.GridLayout();
        let icon = new St.Widget({ layout_manager: layout,
                                   style_class: 'app-folder-icon' });
        layout.hookup_style(icon);
        let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);
        let scale = St.ThemeContext.get_for_stage(global.stage).scale_factor;

        let numItems = this._allItems.length;
        let rtl = icon.get_text_direction() == Clutter.TextDirection.RTL;
        for (let i = 0; i < 4; i++) {
            let bin = new St.Bin({ width: subSize * scale, height: subSize * scale });
            if (i < numItems)
                bin.child = this._allItems[i].app.create_icon_texture(subSize);
            layout.attach(bin, rtl ? (i + 1) % 2 : i % 2, Math.floor(i / 2), 1, 1);
        }

        return icon;
    }

    _onPan(action) {
        let [dist, dx, dy] = action.get_motion_delta(0);
        let adjustment = this.actor.vscroll.adjustment;
        adjustment.value -= (dy / this.actor.height) * adjustment.page_size;
        return false;
    }

    adaptToSize(width, height) {
        this._parentAvailableWidth = width;
        this._parentAvailableHeight = height;

        this._grid.adaptToSize(width, height);

        // To avoid the fade effect being applied to the unscrolled grid,
        // the offset would need to be applied after adjusting the padding;
        // however the final padding is expected to be too small for the
        // effect to look good, so use the unadjusted padding
        let fadeOffset = Math.min(this._grid.topPadding,
                                  this._grid.bottomPadding);
        this.actor.update_fade_effect(fadeOffset, 0);

        // Set extra padding to avoid popup or close button being cut off
        this._grid.topPadding = Math.max(this._grid.topPadding - this._offsetForEachSide, 0);
        this._grid.bottomPadding = Math.max(this._grid.bottomPadding - this._offsetForEachSide, 0);
        this._grid.leftPadding = Math.max(this._grid.leftPadding - this._offsetForEachSide, 0);
        this._grid.rightPadding = Math.max(this._grid.rightPadding - this._offsetForEachSide, 0);

        this.actor.set_width(this.usedWidth());
        this.actor.set_height(this.usedHeight());
    }

    _getPageAvailableSize() {
        let pageBox = new Clutter.ActorBox();
        pageBox.x1 = pageBox.y1 = 0;
        pageBox.x2 = this._parentAvailableWidth;
        pageBox.y2 = this._parentAvailableHeight;

        let contentBox = this.actor.get_theme_node().get_content_box(pageBox);
        // We only can show icons inside the collection view boxPointer
        // so we have to substract the required padding etc of the boxpointer
        return [(contentBox.x2 - contentBox.x1) - 2 * this._offsetForEachSide, (contentBox.y2 - contentBox.y1) - 2 * this._offsetForEachSide];
    }

    usedWidth() {
        let [availWidthPerPage, availHeightPerPage] = this._getPageAvailableSize();
        return this._grid.usedWidth(availWidthPerPage);
    }

    usedHeight() {
        return this._grid.usedHeightForNRows(this.nRowsDisplayedAtOnce());
    }

    nRowsDisplayedAtOnce() {
        let [availWidthPerPage, availHeightPerPage] = this._getPageAvailableSize();
        let maxRows = this._grid.rowsForHeight(availHeightPerPage) - 1;
        return Math.min(this._grid.nRows(availWidthPerPage), maxRows);
    }

    setPaddingOffsets(offset) {
        this._offsetForEachSide = offset;
    }
};

var FolderIcon = class FolderIcon {
    constructor(id, path, parentView) {
        this.id = id;
        this.name = '';
        this._parentView = parentView;

        this._folder = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders.folder',
                                          path: path });
        this.actor = new St.Button({ style_class: 'app-well-app app-folder',
                                     button_mask: St.ButtonMask.ONE,
                                     toggle_mode: true,
                                     can_focus: true,
                                     x_fill: true,
                                     y_fill: true });
        this.actor._delegate = this;
        // whether we need to update arrow side, position etc.
        this._popupInvalidated = false;

        this.icon = new IconGrid.BaseIcon('', { createIcon: this._createIcon.bind(this), setSizeManually: true });
        this.actor.set_child(this.icon);
        this.actor.label_actor = this.icon.label;

        this.view = new FolderView();

        this.actor.connect('clicked', this.open.bind(this));
        this.actor.connect('destroy', this.onDestroy.bind(this));
        this.actor.connect('notify::mapped', () => {
            if (!this.actor.mapped && this._popup)
                this._popup.popdown();
        });

        this._folder.connect('changed', this._redisplay.bind(this));
        this._redisplay();
    }

    onDestroy() {
        this.view.actor.destroy();

        if (this._spaceReadySignalId) {
            this._parentView.disconnect(this._spaceReadySignalId);
            this._spaceReadySignalId = 0;
        }

        if (this._popup)
            this._popup.actor.destroy();
    }

    open() {
        this._ensurePopup();
        this.view.actor.vscroll.adjustment.value = 0;
        this._openSpaceForPopup();
    }

    getAppIds() {
        return this.view.getAllItems().map(item => item.id);
    }

    _updateName() {
        let name = _getFolderName(this._folder);
        if (this.name == name)
            return;

        this.name = name;
        this.icon.label.text = this.name;
        this.emit('name-changed');
    }

    _redisplay() {
        this._updateName();

        this.view.removeAll();

        let excludedApps = this._folder.get_strv('excluded-apps');
        let appSys = Shell.AppSystem.get_default();
        let addAppId = appId => {
            if (this.view.hasItem(appId))
                return;

            if (excludedApps.includes(appId))
                return;

            let app = appSys.lookup_app(appId);
            if (!app)
                return;

            if (!app.get_app_info().should_show())
                return;

            let icon = new AppIcon(app);
            this.view.addItem(icon);
        };

        let folderApps = this._folder.get_strv('apps');
        folderApps.forEach(addAppId);

        let folderCategories = this._folder.get_strv('categories');
        let appInfos = this._parentView.getAppInfos();
        appInfos.forEach(appInfo => {
            let appCategories = _getCategories(appInfo);
            if (!_listsIntersect(folderCategories, appCategories))
                return;

            addAppId(appInfo.get_id());
        });

        this.actor.visible = this.view.getAllItems().length > 0;
        this.view.loadGrid();
        this.emit('apps-changed');
    }

    _createIcon(iconSize) {
        return this.view.createFolderIcon(iconSize, this);
    }

    _popupHeight() {
        let usedHeight = this.view.usedHeight() + this._popup.getOffset(St.Side.TOP) + this._popup.getOffset(St.Side.BOTTOM);
        return usedHeight;
    }

    _openSpaceForPopup() {
        this._spaceReadySignalId = this._parentView.connect('space-ready', () => {
            this._parentView.disconnect(this._spaceReadySignalId);
            this._spaceReadySignalId = 0;
            this._popup.popup();
            this._updatePopupPosition();
        });
        this._parentView.openSpaceForPopup(this, this._boxPointerArrowside, this.view.nRowsDisplayedAtOnce());
    }

    _calculateBoxPointerArrowSide() {
        let spaceTop = this.actor.y - this._parentView.getCurrentPageY();
        let spaceBottom = this._parentView.actor.height - (spaceTop + this.actor.height);

        return spaceTop > spaceBottom ? St.Side.BOTTOM : St.Side.TOP;
    }

    _updatePopupSize() {
        // StWidget delays style calculation until needed, make sure we use the correct values
        this.view._grid.ensure_style();

        let offsetForEachSide = Math.ceil((this._popup.getOffset(St.Side.TOP) +
                                           this._popup.getOffset(St.Side.BOTTOM) -
                                           this._popup.getCloseButtonOverlap()) / 2);
        // Add extra padding to prevent boxpointer decorations and close button being cut off
        this.view.setPaddingOffsets(offsetForEachSide);
        this.view.adaptToSize(this._parentAvailableWidth, this._parentAvailableHeight);
    }

    _updatePopupPosition() {
        if (!this._popup)
            return;

        if (this._boxPointerArrowside == St.Side.BOTTOM)
            this._popup.actor.y = this.actor.allocation.y1 + this.actor.translation_y - this._popupHeight();
        else
            this._popup.actor.y = this.actor.allocation.y1 + this.actor.translation_y + this.actor.height;
    }

    _ensurePopup() {
        if (this._popup && !this._popupInvalidated)
            return;
        this._boxPointerArrowside = this._calculateBoxPointerArrowSide();
        if (!this._popup) {
            this._popup = new AppFolderPopup(this, this._boxPointerArrowside);
            this._parentView.addFolderPopup(this._popup);
            this._popup.connect('open-state-changed', (popup, isOpen) => {
                if (!isOpen)
                    this.actor.checked = false;
            });
        } else {
            this._popup.updateArrowSide(this._boxPointerArrowside);
        }
        this._updatePopupSize();
        this._updatePopupPosition();
        this._popupInvalidated = false;
    }

    adaptToSize(width, height) {
        this._parentAvailableWidth = width;
        this._parentAvailableHeight = height;
        if(this._popup)
            this.view.adaptToSize(width, height);
        this._popupInvalidated = true;
    }
};
Signals.addSignalMethods(FolderIcon.prototype);

var AppFolderPopup = class AppFolderPopup {
    constructor(source, side) {
        this._source = source;
        this._view = source.view;
        this._arrowSide = side;

        this._isOpen = false;
        this.parentOffset = 0;

        this.actor = new St.Widget({ layout_manager: new Clutter.BinLayout(),
                                     visible: false,
                                     // We don't want to expand really, but look
                                     // at the layout manager of our parent...
                                     //
                                     // DOUBLE HACK: if you set one, you automatically
                                     // get the effect for the other direction too, so
                                     // we need to set the y_align
                                     x_expand: true,
                                     y_expand: true,
                                     x_align: Clutter.ActorAlign.CENTER,
                                     y_align: Clutter.ActorAlign.START });
        this._boxPointer = new BoxPointer.BoxPointer(this._arrowSide,
                                                     { style_class: 'app-folder-popup-bin',
                                                       x_fill: true,
                                                       y_fill: true,
                                                       x_expand: true,
                                                       x_align: St.Align.START });

        this._boxPointer.actor.style_class = 'app-folder-popup';
        this.actor.add_actor(this._boxPointer.actor);
        this._boxPointer.bin.set_child(this._view.actor);

        this.closeButton = Util.makeCloseButton(this._boxPointer);
        this.closeButton.connect('clicked', this.popdown.bind(this));
        this.actor.add_actor(this.closeButton);

        this._boxPointer.actor.bind_property('opacity', this.closeButton, 'opacity',
                                             GObject.BindingFlags.SYNC_CREATE);

        global.focus_manager.add_group(this.actor);

        source.actor.connect('destroy', () => { this.actor.destroy(); });
        this._grabHelper = new GrabHelper.GrabHelper(this.actor, {
            actionMode: Shell.ActionMode.POPUP
        });
        this._grabHelper.addActor(Main.layoutManager.overviewGroup);
        this.actor.connect('key-press-event', this._onKeyPress.bind(this));
        this.actor.connect('destroy', this._onDestroy.bind(this));
    }

    _onDestroy() {
        if (this._isOpen) {
            this._isOpen = false;
            this._grabHelper.ungrab({ actor: this.actor });
            this._grabHelper = null;
        }
    }

    _onKeyPress(actor, event) {
        if (global.stage.get_key_focus() != actor)
            return Clutter.EVENT_PROPAGATE;

        // Since we need to only grab focus on one item child when the user
        // actually press a key we don't use navigate_focus when opening
        // the popup.
        // Instead of that, grab the focus on the AppFolderPopup actor
        // and actually moves the focus to a child only when the user
        // actually press a key.
        // It should work with just grab_key_focus on the AppFolderPopup
        // actor, but since the arrow keys are not wrapping_around the focus
        // is not grabbed by a child when the widget that has the current focus
        // is the same that is requesting focus, so to make it works with arrow
        // keys we need to connect to the key-press-event and navigate_focus
        // when that happens using TAB_FORWARD or TAB_BACKWARD instead of arrow
        // keys

        // Use TAB_FORWARD for down key and right key
        // and TAB_BACKWARD for up key and left key on ltr
        // languages
        let direction;
        let isLtr = Clutter.get_default_text_direction() == Clutter.TextDirection.LTR;
        switch (event.get_key_symbol()) {
            case Clutter.Down:
                direction = St.DirectionType.TAB_FORWARD;
                break;
            case Clutter.Right:
                direction = isLtr ? St.DirectionType.TAB_FORWARD :
                                    St.DirectionType.TAB_BACKWARD;
                break;
            case Clutter.Up:
                direction = St.DirectionType.TAB_BACKWARD;
                break;
            case Clutter.Left:
                direction = isLtr ? St.DirectionType.TAB_BACKWARD :
                                    St.DirectionType.TAB_FORWARD;
                break;
            default:
                return Clutter.EVENT_PROPAGATE;
        }
        return actor.navigate_focus(null, direction, false);
    }

    toggle() {
        if (this._isOpen)
            this.popdown();
        else
            this.popup();
    }

    popup() {
        if (this._isOpen)
            return;

        this._isOpen = this._grabHelper.grab({ actor: this.actor,
                                               onUngrab: this.popdown.bind(this) });

        if (!this._isOpen)
            return;

        this.actor.show();

        this._boxPointer.setArrowActor(this._source.actor);
        // We need to hide the icons of the view until the boxpointer animation
        // is completed so we can animate the icons after as we like without
        // showing them while boxpointer is animating.
        this._view.actor.opacity = 0;
        this._boxPointer.open(BoxPointer.PopupAnimation.FADE |
                              BoxPointer.PopupAnimation.SLIDE,
                              () => {
                this._view.actor.opacity = 255;
                this._view.animate(IconGrid.AnimationDirection.IN);
            });

        this.emit('open-state-changed', true);
    }

    popdown() {
        if (!this._isOpen)
            return;

        this._grabHelper.ungrab({ actor: this.actor });

        this._boxPointer.close(BoxPointer.PopupAnimation.FADE |
                               BoxPointer.PopupAnimation.SLIDE);
        this._isOpen = false;
        this.emit('open-state-changed', false);
    }

    getCloseButtonOverlap() {
        return this.closeButton.get_theme_node().get_length('-shell-close-overlap-y');
    }

    getOffset(side) {
        let offset = this._boxPointer.getPadding(side);
        if (this._arrowSide == side)
            offset += this._boxPointer.getArrowHeight();
        return offset;
    }

    updateArrowSide(side) {
        this._arrowSide = side;
        this._boxPointer.updateArrowSide(side);
    }
};
Signals.addSignalMethods(AppFolderPopup.prototype);

var AppIcon = class AppIcon {
    constructor(app, iconParams) {
        this.app = app;
        this.id = app.get_id();
        this.name = app.get_name();

        this.actor = new St.Button({ style_class: 'app-well-app',
                                     reactive: true,
                                     button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
                                     can_focus: true,
                                     x_fill: true,
                                     y_fill: true });

        this._dot = new St.Widget({ style_class: 'app-well-app-running-dot',
                                    layout_manager: new Clutter.BinLayout(),
                                    x_expand: true, y_expand: true,
                                    x_align: Clutter.ActorAlign.CENTER,
                                    y_align: Clutter.ActorAlign.END });

        this._iconContainer = new St.Widget({ layout_manager: new Clutter.BinLayout(),
                                              x_expand: true, y_expand: true });

        this.actor.set_child(this._iconContainer);
        this._iconContainer.add_child(this._dot);

        this.actor._delegate = this;

        if (!iconParams)
            iconParams = {};

        // Get the isDraggable property without passing it on to the BaseIcon:
        let appIconParams = Params.parse(iconParams, { isDraggable: true }, true);
        let isDraggable = appIconParams['isDraggable'];
        delete iconParams['isDraggable'];

        iconParams['createIcon'] = this._createIcon.bind(this);
        iconParams['setSizeManually'] = true;
        this.icon = new IconGrid.BaseIcon(app.get_name(), iconParams);
        this._iconContainer.add_child(this.icon);

        this.actor.label_actor = this.icon.label;

        this.actor.connect('leave-event', this._onLeaveEvent.bind(this));
        this.actor.connect('button-press-event', this._onButtonPress.bind(this));
        this.actor.connect('touch-event', this._onTouchEvent.bind(this));
        this.actor.connect('clicked', this._onClicked.bind(this));
        this.actor.connect('popup-menu', this._onKeyboardPopupMenu.bind(this));

        this._hoverText = null;
        this._hoverTimeoutId = 0;

        if (this.icon.label) {
            this._hoverText = new St.Label({ style_class: 'app-well-hover-text',
                                             text: this.icon.label.text,
                                             visible: false });
            this._hoverText.clutter_text.line_wrap = true;
            Main.layoutManager.addChrome(this._hoverText);

            this.actor.connect('notify::hover', this._syncHoverText.bind(this));
            this.connect('sync-tooltip', this._syncHoverText.bind(this));
        }

        this._menu = null;
        this._menuManager = new PopupMenu.PopupMenuManager(this);

        if (isDraggable) {
            this._draggable = DND.makeDraggable(this.actor);
            this._draggable.connect('drag-begin', () => {
                this._removeMenuTimeout();
                Main.overview.beginItemDrag(this);
            });
            this._draggable.connect('drag-cancelled', () => {
                Main.overview.cancelledItemDrag(this);
            });
            this._draggable.connect('drag-end', () => {
               Main.overview.endItemDrag(this);
            });
        }

        this.actor.connect('destroy', this._onDestroy.bind(this));

        this._menuTimeoutId = 0;
        this._stateChangedId = this.app.connect('notify::state', () => {
            this._updateRunningStyle();
        });
        this._updateRunningStyle();
    }

    _onDestroy() {
        if (this._stateChangedId > 0)
            this.app.disconnect(this._stateChangedId);
        this._stateChangedId = 0;
        this._removeMenuTimeout();
        this._removeHoverTimeout();
        if (this._hoverText)
            this._hoverText.destroy();
        this._hoverText = null;
    }

    _createIcon(iconSize) {
        return this.app.create_icon_texture(iconSize);
    }

    _syncHoverText() {
        if (this.shouldShowTooltip()) {
            if (this._hoverTimeoutId)
                return;

            this._hoverTimeoutId = Mainloop.timeout_add(300, () => {
                this._hoverText.style = `max-width: ${2 * this.icon.iconSize}px;`;
                this._hoverText.ensure_style();

                let [x, y] = this.icon.label.get_transformed_position();
                let offset = (this._hoverText.width - this.icon.label.width) / 2;
                this._hoverText.set_position(Math.floor(x - offset), Math.floor(y));
                this._hoverText.show();

                this._hoverTimeoutId = 0;
                return GLib.SOURCE_REMOVE;
            });
        } else {
            this._removeHoverTimeout();
            this._hoverText.hide();
        }
    }

    _removeMenuTimeout() {
        if (this._menuTimeoutId > 0) {
            Mainloop.source_remove(this._menuTimeoutId);
            this._menuTimeoutId = 0;
        }
    }

    _removeHoverTimeout() {
        if (this._hoverTimeoutId > 0) {
            Mainloop.source_remove(this._hoverTimeoutId);
            this._hoverTimeoutId = 0;
        }
    }

    _updateRunningStyle() {
        if (this.app.state != Shell.AppState.STOPPED)
            this._dot.show();
        else
            this._dot.hide();
    }

    _setPopupTimeout() {
        this._removeMenuTimeout();
        this._menuTimeoutId = Mainloop.timeout_add(MENU_POPUP_TIMEOUT, () => {
            this._menuTimeoutId = 0;
            this.popupMenu();
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(this._menuTimeoutId, '[gnome-shell] this.popupMenu');
    }

    _onLeaveEvent(actor, event) {
        this.actor.fake_release();
        this._removeMenuTimeout();
    }

    _onButtonPress(actor, event) {
        let button = event.get_button();
        if (button == 1) {
            this._setPopupTimeout();
        } else if (button == 3) {
            this.popupMenu();
            return Clutter.EVENT_STOP;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onTouchEvent(actor, event) {
        if (event.type() == Clutter.EventType.TOUCH_BEGIN)
            this._setPopupTimeout();

        return Clutter.EVENT_PROPAGATE;
    }

    _onClicked(actor, button) {
        this._removeMenuTimeout();
        this.activate(button);
    }

    _onKeyboardPopupMenu() {
        this.popupMenu();
        this._menu.actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
    }

    getId() {
        return this.app.get_id();
    }

    popupMenu() {
        this._removeMenuTimeout();
        this.actor.fake_release();

        if (this._draggable)
            this._draggable.fakeRelease();

        if (!this._menu) {
            this._menu = new AppIconMenu(this);
            this._menu.connect('activate-window', (menu, window) => {
                this.activateWindow(window);
            });
            this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
                if (!isPoppedUp)
                    this._onMenuPoppedDown();
            });
            let id = Main.overview.connect('hiding', () => {
                this._menu.close();
            });
            this.actor.connect('destroy', () => {
                Main.overview.disconnect(id);
            });

            this._menuManager.addMenu(this._menu);
        }

        this.emit('menu-state-changed', true);

        this.actor.set_hover(true);
        this._menu.popup();
        this._menuManager.ignoreRelease();
        this.emit('sync-tooltip');

        return false;
    }

    activateWindow(metaWindow) {
        if (metaWindow) {
            Main.activateWindow(metaWindow);
        } else {
            Main.overview.hide();
        }
    }

    _onMenuPoppedDown() {
        this.actor.sync_hover();
        this.emit('menu-state-changed', false);
    }

    activate(button) {
        let event = Clutter.get_current_event();
        let modifiers = event ? event.get_state() : 0;
        let isMiddleButton = button && button == Clutter.BUTTON_MIDDLE;
        let isCtrlPressed = (modifiers & Clutter.ModifierType.CONTROL_MASK) != 0;
        let openNewWindow = this.app.can_open_new_window() &&
                            this.app.state == Shell.AppState.RUNNING &&
                            (isCtrlPressed || isMiddleButton);

        if (this.app.state == Shell.AppState.STOPPED || openNewWindow)
            this.animateLaunch();

        if (openNewWindow)
            this.app.open_new_window(-1);
        else
            this.app.activate();

        Main.overview.hide();
    }

    animateLaunch() {
        this.icon.animateZoomOut();
    }

    shellWorkspaceLaunch(params) {
        params = Params.parse(params, { workspace: -1,
                                        timestamp: 0 });

        this.app.open_new_window(params.workspace);
    }

    getDragActor() {
        return this.app.create_icon_texture(Main.overview.dashIconSize);
    }

    // Returns the original actor that should align with the actor
    // we show as the item is being dragged.
    getDragActorSource() {
        return this.icon.icon;
    }

    shouldShowTooltip() {
        return this.actor.hover && (!this._menu || !this._menu.isOpen);
    }
};
Signals.addSignalMethods(AppIcon.prototype);

var AppIconMenu = class AppIconMenu extends PopupMenu.PopupMenu {
    constructor(source) {
        let side = St.Side.LEFT;
        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL)
            side = St.Side.RIGHT;

        super(source.actor, 0.5, side);

        // We want to keep the item hovered while the menu is up
        this.blockSourceEvents = true;

        this._source = source;

        this.actor.add_style_class_name('app-well-menu');

        // Chain our visibility and lifecycle to that of the source
        this._sourceMappedId = source.actor.connect('notify::mapped', () => {
            if (!source.actor.mapped)
                this.close();
        });
        source.actor.connect('destroy', () => {
            source.actor.disconnect(this._sourceMappedId);
            this.destroy();
        });

        Main.uiGroup.add_actor(this.actor);
    }

    _redisplay() {
        this.removeAll();

        let windows = this._source.app.get_windows().filter(
            w => !w.skip_taskbar
        );

        // Display the app windows menu items and the separator between windows
        // of the current desktop and other windows.
        let workspaceManager = global.workspace_manager;
        let activeWorkspace = workspaceManager.get_active_workspace();
        let separatorShown = windows.length > 0 && windows[0].get_workspace() != activeWorkspace;

        for (let i = 0; i < windows.length; i++) {
            let window = windows[i];
            if (!separatorShown && window.get_workspace() != activeWorkspace) {
                this._appendSeparator();
                separatorShown = true;
            }
            let title = window.title ? window.title
                                     : this._source.app.get_name();
            let item = this._appendMenuItem(title);
            item.connect('activate', () => {
                this.emit('activate-window', window);
            });
        }

        if (!this._source.app.is_window_backed()) {
            this._appendSeparator();

            let appInfo = this._source.app.get_app_info();
            let actions = appInfo.list_actions();
            if (this._source.app.can_open_new_window() &&
                actions.indexOf('new-window') == -1) {
                this._newWindowMenuItem = this._appendMenuItem(_("New Window"));
                this._newWindowMenuItem.connect('activate', () => {
                    if (this._source.app.state == Shell.AppState.STOPPED)
                        this._source.animateLaunch();

                    this._source.app.open_new_window(-1);
                    this.emit('activate-window', null);
                });
                this._appendSeparator();
            }

            if (discreteGpuAvailable &&
                this._source.app.state == Shell.AppState.STOPPED &&
                actions.indexOf('activate-discrete-gpu') == -1) {
                this._onDiscreteGpuMenuItem = this._appendMenuItem(_("Launch using Dedicated Graphics Card"));
                this._onDiscreteGpuMenuItem.connect('activate', () => {
                    if (this._source.app.state == Shell.AppState.STOPPED)
                        this._source.animateLaunch();

                    this._source.app.launch(0, -1, true);
                    this.emit('activate-window', null);
                });
            }

            for (let i = 0; i < actions.length; i++) {
                let action = actions[i];
                let item = this._appendMenuItem(appInfo.get_action_name(action));
                item.connect('activate', (emitter, event) => {
                    this._source.app.launch_action(action, event.get_time(), -1);
                    this.emit('activate-window', null);
                });
            }

            let canFavorite = global.settings.is_writable('favorite-apps');

            if (canFavorite) {
                this._appendSeparator();

                let isFavorite = AppFavorites.getAppFavorites().isFavorite(this._source.app.get_id());

                if (isFavorite) {
                    let item = this._appendMenuItem(_("Remove from Favorites"));
                    item.connect('activate', () => {
                        let favs = AppFavorites.getAppFavorites();
                        favs.removeFavorite(this._source.app.get_id());
                    });
                } else {
                    let item = this._appendMenuItem(_("Add to Favorites"));
                    item.connect('activate', () => {
                        let favs = AppFavorites.getAppFavorites();
                        favs.addFavorite(this._source.app.get_id());
                    });
                }
            }

            if (Shell.AppSystem.get_default().lookup_app('org.gnome.Software.desktop')) {
                this._appendSeparator();
                let item = this._appendMenuItem(_("Show Details"));
                item.connect('activate', () => {
                    let id = this._source.app.get_id();
                    let args = GLib.Variant.new('(ss)', [id, '']);
                    Gio.DBus.get(Gio.BusType.SESSION, null, (o, res) => {
                        let bus = Gio.DBus.get_finish(res);
                        bus.call('org.gnome.Software',
                                 '/org/gnome/Software',
                                 'org.gtk.Actions', 'Activate',
                                 GLib.Variant.new('(sava{sv})',
                                                  ['details', [args], null]),
                                 null, 0, -1, null, null);
                        Main.overview.hide();
                    });
                });
            }
        }
    }

    _appendSeparator() {
        let separator = new PopupMenu.PopupSeparatorMenuItem();
        this.addMenuItem(separator);
    }

    _appendMenuItem(labelText) {
        // FIXME: app-well-menu-item style
        let item = new PopupMenu.PopupMenuItem(labelText);
        this.addMenuItem(item);
        return item;
    }

    popup(activatingButton) {
        this._redisplay();
        this.open();
    }
};
Signals.addSignalMethods(AppIconMenu.prototype);

var SystemActionIcon = class SystemActionIcon extends Search.GridSearchResult {
    activate() {
        SystemActions.getDefault().activateAction(this.metaInfo['id']);
        Main.overview.hide();
    }
};
(uuay)components/58i�[Qckeyring.js�*// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter, Gcr, Gio, GObject, Pango, Shell, St } = imports.gi;

const Animation = imports.ui.animation;
const Dialog = imports.ui.dialog;
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const CheckBox = imports.ui.checkBox;

var WORK_SPINNER_ICON_SIZE = 16;

var KeyringDialog = class extends ModalDialog.ModalDialog {
    constructor() {
        super({ styleClass: 'prompt-dialog' });

        this.prompt = new Shell.KeyringPrompt();
        this.prompt.connect('show-password', this._onShowPassword.bind(this));
        this.prompt.connect('show-confirm', this._onShowConfirm.bind(this));
        this.prompt.connect('prompt-close', this._onHidePrompt.bind(this));

        let icon = new Gio.ThemedIcon({ name: 'dialog-password-symbolic' });
        this._content = new Dialog.MessageDialogContent({ icon });
        this.contentLayout.add(this._content);

        // FIXME: Why does this break now?
        /*
        this.prompt.bind_property('message', this._content, 'title', GObject.BindingFlags.SYNC_CREATE);
        this.prompt.bind_property('description', this._content, 'body', GObject.BindingFlags.SYNC_CREATE);
        */
        this.prompt.connect('notify::message', () => {
            this._content.title = this.prompt.message;
        });
        this._content.title = this.prompt.message;

        this.prompt.connect('notify::description', () => {
            this._content.body = this.prompt.description;
        });
        this._content.body = this.prompt.description;

        this._workSpinner = null;
        this._controlTable = null;

        this._cancelButton = this.addButton({ label: '',
                                              action: this._onCancelButton.bind(this),
                                              key: Clutter.Escape });
        this._continueButton = this.addButton({ label: '',
                                                action: this._onContinueButton.bind(this),
                                                default: true });

        this.prompt.bind_property('cancel-label', this._cancelButton, 'label', GObject.BindingFlags.SYNC_CREATE);
        this.prompt.bind_property('continue-label', this._continueButton, 'label', GObject.BindingFlags.SYNC_CREATE);
    }

    _setWorking(working) {
        if (!this._workSpinner)
            return;

        if (working)
            this._workSpinner.play();
        else
            this._workSpinner.stop();
    }

    _buildControlTable() {
        let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
        let table = new St.Widget({ style_class: 'keyring-dialog-control-table',
                                    layout_manager: layout });
        layout.hookup_style(table);
        let rtl = table.get_text_direction() == Clutter.TextDirection.RTL;
        let row = 0;

        if (this.prompt.password_visible) {
            let label = new St.Label({ style_class: 'prompt-dialog-password-label',
                                       x_align: Clutter.ActorAlign.START,
                                       y_align: Clutter.ActorAlign.CENTER });
            label.set_text(_("Password:"));
            label.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
            this._passwordEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
                                                 text: '',
                                                 can_focus: true,
                                                 x_expand: true });
            this._passwordEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
            ShellEntry.addContextMenu(this._passwordEntry, { isPassword: true });
            this._passwordEntry.clutter_text.connect('activate', this._onPasswordActivate.bind(this));

            this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, true);

            if (rtl) {
                layout.attach(this._workSpinner.actor, 0, row, 1, 1);
                layout.attach(this._passwordEntry, 1, row, 1, 1);
                layout.attach(label, 2, row, 1, 1);
            } else {
                layout.attach(label, 0, row, 1, 1);
                layout.attach(this._passwordEntry, 1, row, 1, 1);
                layout.attach(this._workSpinner.actor, 2, row, 1, 1);
            }
            row++;
        } else {
            this._workSpinner = null;
            this._passwordEntry = null;
        }

        if (this.prompt.confirm_visible) {
            var label = new St.Label(({ style_class: 'prompt-dialog-password-label',
                                        x_align: Clutter.ActorAlign.START,
                                        y_align: Clutter.ActorAlign.CENTER }));
            label.set_text(_("Type again:"));
            this._confirmEntry = new St.Entry({ style_class: 'prompt-dialog-password-entry',
                                                text: '',
                                                can_focus: true,
                                                x_expand: true });
            this._confirmEntry.clutter_text.set_password_char('\u25cf'); // ● U+25CF BLACK CIRCLE
            ShellEntry.addContextMenu(this._confirmEntry, { isPassword: true });
            this._confirmEntry.clutter_text.connect('activate', this._onConfirmActivate.bind(this));
            if (rtl) {
                layout.attach(this._confirmEntry, 0, row, 1, 1);
                layout.attach(label, 1, row, 1, 1);
            } else {
                layout.attach(label, 0, row, 1, 1);
                layout.attach(this._confirmEntry, 1, row, 1, 1);
            }
            row++;
        } else {
            this._confirmEntry = null;
        }

        this.prompt.set_password_actor(this._passwordEntry ? this._passwordEntry.clutter_text : null);
        this.prompt.set_confirm_actor(this._confirmEntry ? this._confirmEntry.clutter_text : null);

        if (this._passwordEntry || this._confirmEntry) {
            this._capsLockWarningLabel = new ShellEntry.CapsLockWarning();
            layout.attach(this._capsLockWarningLabel, 1, row, 1, 1);
            row++;
        }

        if (this.prompt.choice_visible) {
            let choice = new CheckBox.CheckBox();
            this.prompt.bind_property('choice-label', choice.getLabelActor(), 'text', GObject.BindingFlags.SYNC_CREATE);
            this.prompt.bind_property('choice-chosen', choice.actor, 'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
            layout.attach(choice.actor, rtl ? 0 : 1, row, 1, 1);
            row++;
        }

        let warning = new St.Label({ style_class: 'prompt-dialog-error-label',
                                     x_align: Clutter.ActorAlign.START });
        warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        warning.clutter_text.line_wrap = true;
        layout.attach(warning, rtl ? 0 : 1, row, 1, 1);
        this.prompt.bind_property('warning-visible', warning, 'visible', GObject.BindingFlags.SYNC_CREATE);
        this.prompt.bind_property('warning', warning, 'text', GObject.BindingFlags.SYNC_CREATE);

        if (this._controlTable) {
            this._controlTable.destroy_all_children();
            this._controlTable.destroy();
        }

        this._controlTable = table;
        this._content.messageBox.add(table, { x_fill: true, y_fill: true });
    }

    _updateSensitivity(sensitive) {
        if (this._passwordEntry) {
            this._passwordEntry.reactive = sensitive;
            this._passwordEntry.clutter_text.editable = sensitive;
        }

        if (this._confirmEntry) {
            this._confirmEntry.reactive = sensitive;
            this._confirmEntry.clutter_text.editable = sensitive;
        }

        this._continueButton.can_focus = sensitive;
        this._continueButton.reactive = sensitive;
        this._setWorking(!sensitive);
    }

    _ensureOpen() {
        // NOTE: ModalDialog.open() is safe to call if the dialog is
        // already open - it just returns true without side-effects
        if (this.open())
          return true;

        // The above fail if e.g. unable to get input grab
        //
        // In an ideal world this wouldn't happen (because the
        // Shell is in complete control of the session) but that's
        // just not how things work right now.

        log('keyringPrompt: Failed to show modal dialog.' +
            ' Dismissing prompt request');
        this.prompt.cancel()
        return false;
    }

    _onShowPassword(prompt) {
        this._buildControlTable();
        this._ensureOpen();
        this._updateSensitivity(true);
        this._passwordEntry.grab_key_focus();
    }

    _onShowConfirm(prompt) {
        this._buildControlTable();
        this._ensureOpen();
        this._updateSensitivity(true);
        this._continueButton.grab_key_focus();
    }

    _onHidePrompt(prompt) {
        this.close();
    }

    _onPasswordActivate() {
        if (this.prompt.confirm_visible)
            this._confirmEntry.grab_key_focus();
        else
            this._onContinueButton();
    }

    _onConfirmActivate() {
        this._onContinueButton();
    }

    _onContinueButton() {
        this._updateSensitivity(false);
        this.prompt.complete();
    }

    _onCancelButton() {
        this.prompt.cancel();
    }
};

var KeyringDummyDialog = class {
    constructor() {
        this.prompt = new Shell.KeyringPrompt();
        this.prompt.connect('show-password', this._cancelPrompt.bind(this));
        this.prompt.connect('show-confirm', this._cancelPrompt.bind(this));
    }

    _cancelPrompt() {
        this.prompt.cancel();
    }
};

var KeyringPrompter = class {
    constructor() {
        this._prompter = new Gcr.SystemPrompter();
        this._prompter.connect('new-prompt', () => {
            let dialog = this._enabled ? new KeyringDialog()
                                       : new KeyringDummyDialog();
            this._currentPrompt = dialog.prompt;
            return this._currentPrompt;
        });
        this._dbusId = null;
        this._registered = false;
        this._enabled = false;
        this._currentPrompt = null;
    }

    enable() {
        if (!this._registered) {
            this._prompter.register(Gio.DBus.session);
            this._dbusId = Gio.DBus.session.own_name('org.gnome.keyring.SystemPrompter',
                                                     Gio.BusNameOwnerFlags.ALLOW_REPLACEMENT, null, null);
            this._registered = true;
        }
        this._enabled = true;
    }

    disable() {
        this._enabled = false;

        if (this._prompter.prompting)
            this._currentPrompt.cancel();
        this._currentPrompt = null;
    }
};

var Component = KeyringPrompter;
(uuay)weather.js // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Geoclue, Gio, GLib, GWeather } = imports.gi;
const Signals = imports.signals;

const PermissionStore = imports.misc.permissionStore;
const Util = imports.misc.util;

// Minimum time between updates to show loading indication
var UPDATE_THRESHOLD = 10 * GLib.TIME_SPAN_MINUTE;

var WeatherClient = class {
    constructor() {
        this._loading = false;
        this._locationValid = false;
        this._lastUpdate = GLib.DateTime.new_from_unix_local(0);

        this._autoLocationRequested = false;
        this._mostRecentLocation = null;

        this._gclueService = null;
        this._gclueStarted = false;
        this._gclueStarting = false;
        this._gclueLocationChangedId = 0;

        this._weatherAuthorized = false;
        this._permStore = new PermissionStore.PermissionStore((proxy, error) => {
            if (error) {
                log('Failed to connect to permissionStore: ' + error.message);
                return;
            }

            if (this._permStore.g_name_owner == null) {
                // Failed to auto-start, likely because xdg-desktop-portal
                // isn't installed; don't restrict access to location service
                this._weatherAuthorized = true;
                this._updateAutoLocation();
                return;
            }

            this._permStore.LookupRemote('gnome', 'geolocation', (res, error) => {
                if (error)
                    log('Error looking up permission: ' + error.message);

                let [perms, data] = error ? [{}, null] : res;
                let  params = ['gnome', 'geolocation', false, data, perms];
                this._onPermStoreChanged(this._permStore, '', params);
            });
        });
        this._permStore.connectSignal('Changed',
                                      this._onPermStoreChanged.bind(this));

        this._locationSettings = new Gio.Settings({ schema_id: 'org.gnome.system.location' });
        this._locationSettings.connect('changed::enabled',
                                       this._updateAutoLocation.bind(this));

        this._world = GWeather.Location.get_world();

        this._providers = GWeather.Provider.METAR |
                          GWeather.Provider.YR_NO |
                          GWeather.Provider.OWM;

        this._weatherInfo = new GWeather.Info({ enabled_providers: 0 });
        this._weatherInfo.connect_after('updated', () => {
            this._lastUpdate = GLib.DateTime.new_now_local();
            this.emit('changed');
        });

        this._weatherAppMon = new Util.AppSettingsMonitor('org.gnome.Weather.desktop',
                                                          'org.gnome.Weather');
        this._weatherAppMon.connect('available-changed', () => { this.emit('changed'); });
        this._weatherAppMon.watchSetting('automatic-location',
                                         this._onAutomaticLocationChanged.bind(this));
        this._weatherAppMon.watchSetting('locations',
                                         this._onLocationsChanged.bind(this));
    }

    get available() {
        return this._weatherAppMon.available;
    }

    get loading() {
        return this._loading;
    }

    get hasLocation() {
        return this._locationValid;
    }

    get info() {
        return this._weatherInfo;
    }

    activateApp() {
        this._weatherAppMon.activateApp();
    }

    update() {
        if (!this._locationValid)
            return;

        let now = GLib.DateTime.new_now_local();
        // Update without loading indication if the current info is recent enough
        if (this._weatherInfo.is_valid() &&
            now.difference(this._lastUpdate) < UPDATE_THRESHOLD)
            this._weatherInfo.update();
        else
            this._loadInfo();
    }

    get _useAutoLocation() {
        return this._autoLocationRequested &&
               this._locationSettings.get_boolean('enabled') &&
               this._weatherAuthorized;
    }

    _loadInfo() {
        let id = this._weatherInfo.connect('updated', () => {
            this._weatherInfo.disconnect(id);
            this._loading = false;
        });

        this._loading = true;
        this.emit('changed');

        this._weatherInfo.update();
    }

    _locationsEqual(loc1, loc2) {
        if (loc1 == loc2)
            return true;

        if (loc1 == null || loc2 == null)
            return false;

        return loc1.equal(loc2);
    }

    _setLocation(location) {
        if (this._locationsEqual(this._weatherInfo.location, location))
            return;

        this._weatherInfo.abort();
        this._weatherInfo.set_location(location);
        this._locationValid = (location != null);

        this._weatherInfo.set_enabled_providers(location ? this._providers : 0);

        if (location)
            this._loadInfo();
        else
            this.emit('changed');
    }

    _updateLocationMonitoring() {
        if (this._useAutoLocation) {
            if (this._gclueLocationChangedId != 0 || this._gclueService == null)
                return;

            this._gclueLocationChangedId =
                this._gclueService.connect('notify::location',
                                           this._onGClueLocationChanged.bind(this));
            this._onGClueLocationChanged();
        } else {
            if (this._gclueLocationChangedId)
                this._gclueService.disconnect(this._gclueLocationChangedId);
            this._gclueLocationChangedId = 0;
        }
    }

    _startGClueService() {
        if (this._gclueStarting)
            return;

        this._gclueStarting = true;

        Geoclue.Simple.new('org.gnome.Shell', Geoclue.AccuracyLevel.CITY, null,
            (o, res) => {
                try {
                    this._gclueService = Geoclue.Simple.new_finish(res);
                } catch(e) {
                    log('Failed to connect to Geoclue2 service: ' + e.message);
                    this._setLocation(this._mostRecentLocation);
                    return;
                }
                this._gclueStarted = true;
                this._gclueService.get_client().distance_threshold = 100;
                this._updateLocationMonitoring();
            });
    }

    _onGClueLocationChanged() {
        let geoLocation = this._gclueService.location;
        let location = GWeather.Location.new_detached(geoLocation.description,
                                                      null,
                                                      geoLocation.latitude,
                                                      geoLocation.longitude);
        this._setLocation(location);
    }

    _onAutomaticLocationChanged(settings, key) {
        let useAutoLocation = settings.get_boolean(key);
        if (this._autoLocationRequested == useAutoLocation)
            return;

        this._autoLocationRequested = useAutoLocation;

        this._updateAutoLocation();
    }

    _updateAutoLocation() {
        this._updateLocationMonitoring();

        if (this._useAutoLocation)
            this._startGClueService();
        else
            this._setLocation(this._mostRecentLocation);
    }

    _onLocationsChanged(settings, key) {
        let serialized = settings.get_value(key).deep_unpack().shift();
        let mostRecentLocation = null;

        if (serialized)
            mostRecentLocation = this._world.deserialize(serialized);

        if (this._locationsEqual(this._mostRecentLocation, mostRecentLocation))
            return;

        this._mostRecentLocation = mostRecentLocation;

        if (!this._useAutoLocation || !this._gclueStarted)
            this._setLocation(this._mostRecentLocation);
    }

    _onPermStoreChanged(proxy, sender, params) {
        let [table, id, deleted, data, perms] = params;

        if (table != 'gnome' || id != 'geolocation')
            return;

        let permission = perms['org.gnome.Weather'] || ['NONE'];
        let [accuracy] = permission;
        this._weatherAuthorized = accuracy != 'NONE';

        this._updateAutoLocation();
    }
};
Signals.addSignalMethods(WeatherClient.prototype);
(uuay);�o����P����
��8�49��dK�e`��}u��~���~�������@��P�t ���� � ��@ �`0��@��P��`��`�`��|@�����@� �X������ ��0��P���$��Dp�p��0��`����@0�l���@��P���H` ��� ��p!��!� �!�4�!�P�"��P#���$��%��%�x&���&�,`'��p'��0(�$0)�T�)�|�*�� ,��.�TP1���1���2�$�3�X@5���5��`6���6� �6�, �6�@ �6�T  7�p @7�� `7�� �7�� �7�� �7�!8�@!08�X!`8��!�8��!�8��!�8��!�8�"09�8"P9�T"p9�p"�9��"�9��";��"�;��"p=�(#�=�<#�=�h#>��#`>��#�>��#?��#�?��#�?�0$�@��$�@��$`A��$�A��$ B��$�B�(%�D�X%�D��%PE��%PF��%�F��%G�&�H�8&@I�`&�I��&�I��&�J��&�K� '�K�4'@L�`'�N��'�N��'PO�(�O�<( P�X(�P��(PQ��(�Q��(�R��)�T��)�U��)�U�* V�D*�V��*�W��*�X�+�Y�\+�Y�p+0Z��+0[��+@[�, \�X,�^��,0_��,P_��,a�@-Pa�`-`b��-c��-�c�.�d�<.e�h.Pe��.�e��.`f�/Pg�d/�g��/0h��/�h��/0i�(0Pi�@0�i�l0j��0�j��0�k�1 m�X1�m��1Po��1`o��1�o�2`p�L2Pr��2�r��2�s�3�v�P3�w�|3�w��3�x��3 y�$4`y�@4py�T4�y�h4@z��4�z��4`{��4�{�5 |�D5�|��5�}��5�6��\6��|60���6���6��@7@��T7���|7����7���7���7Ј� 8@���8P��L9���90���9p���9���:0��@:��t:����:@���:���:0��d;����;В��;���;P���;`��<p��<���(<���x<����<����<p���<��= ��$=p��@=p��p=����=����=��>��$>0��D>P��d>����>@���>����>��� ? ��`?0��t?����?���?`���?��@@��T@��h@0��|@@���@P���@���@���@���A���DA��`A����A����A����AЪ�B��$B��PB`��|B����B���B���B0��XC���C0���C����C��D��LD���D@���D��E���lE���E����E`���E��$F`��LF����F���F0���F@��G���G���dG����G����G`��H ��pH���H@���H ���H��,I���TI���pI����IP���I����I���XJ����J����J���`K����K���LP��0Lp��DL���XL���lL`���L���L����L@��(M`��@Mp��TM���hM����M����Mp��N���0N0��|N����N ��,O���\O����O����O���P0��0P@��DPP��pP����P����P@���P���@Qp���Q����Q����Q ���Q���8R���PR`���R����R���0S���DS����S����S����S���,T0��XT`���T����T����TP��U���hU����U����U@���U����U��8V0��TV`��tV����Vp��(W���dW���W����W���PX����X ��YP��`Y���|Y���Y`��PZ�|Z��Z��[0�h[���[�\��L\�h\ �|\���\p��\0� ]�	�l]
��]`
��]�
��]�
��]�^��0^`�l^�
��^`�_P�H_��x_��_ ��_���_0�`0�d` ��`���`�@a��ha���a��a`�b��Lb��hb@��b���b���b@ �Tc!��cp!��c�!�d�!�(d�!�<dp#��d $�4eP$�Pe�$�pep%��ep&��e '�f�'�0f`(�hfp(�|f�(��fP)��f*�g0*�,g`*�Lgp,��g�-��g /�h�/�4h�/�Ph0�xhP0��h1��h�1� i 2�Li3��i@3��i�3��i 4�<j05�xj`5��jp6��j�6��j 7�,kP7�DkP:��k�;��k�;�l�;�$l0<�Llp<�hl�<�|l�<��l�@��m0A�nPA�n`A�,npA�@n�A�Tn�A�hn�A�|n�A��n�A��n�A��nB��n B��n@B��n`B�o�B�$o�B�8o�B�LoC�`o@C�|o`C��o�C��o�C��o�C��o@D��o�D�p�D�(p�D�DpPE�`p�E�|p�E��p�F��p�G�8q�H�lqI��q�I��q�I��q`J�,r N�|r�N��rQ�sR��s0S��s�S��sPT�(t�W��t�X��t`Y� u�[�pu�\��u�\��u�^��u�^�v_�,v0_�Dv�_�pv�c��vd��vPd�w�d� w�d�<w e�\w�e��w`f��w�f�xg�$x�g�Px�h��x i��x�i�8yk��y�t��y�u�z�v�<z�w�hz�w�|zPx��z�x��z y�{�y�<{�y�p{Pz��{�z��{`|��|�|��|�}��|p~�4}�X}`�x}���}@��~���~ �����@@��`�������0��,�@��X������`�������� ������T�@���������������$�p��L����`� ������؂������(�P��P����x������������p�`���������`�������@��\�����������З���P��ȅ����И��p��8����t�p����0������$���������������H�`��d����Ч�0� ��X�0�������`��X��������܊���0�����|������������D������zRx�$�|��>FJw�?:*3$"D����>\8��vd[
Au|���vd[
Au����vd[
Au�X��vd[
Au�������������0���D����X|���F�B�B �A(�A0�L8H@UHRPBXB`L0I8H@MHMPDXB`BhBpI8H@OHKPBXB`BhBpP0[(A EBB����E�P4�����F�A�A ��(L0P(D AAB40����E�A�G0A
AAHP8]@H8A0$h��UE�A�G AAA@�$���B�A�A �D@�
 GABMx
 AABD0����E�A�D k
DAFTAA,�$(�DE�A�G jDDDP�	XL�E�Q(tP��E�D�D 
AAJ����E��
G(����a�X
GPH bA_L����F�E�B �B(�A0�K8�J�Q
8A0A(B BBBA<8�"E�WXL�'E�[Dt`�eF�N�E �D(�D0�Z8H@X8A0A(A BBB(���]F�D�D �HAE@����F�N�D �D(�D0\8G@Y8A0m(A ABB<,�fF�N�D �D(�D0\8H@[(A ABBlH�	@�D��F�N�D �D(�D0\8G@Y8A0m(A ABB<���fF�N�D �D(�D0\8H@[(A ABB4��oF�E�D �D(�G0N(A ABB@<��F�G�K �D(�D0\8I@Y8A0p(A ABB�d�"E�U�x��t�$E�W,����E�D0{
AKh8L@c8A0$�X�dE�C�G PCAH$��vF�B�E �B(�H0�D8�G`!
8A0A(B BBBCp�	�<E�qd��	��F�E�E �E(�D0�D8�G@NHHPVXL`AhDpAxG�A�E�Q8A0A(B BBB4� 
�gF�D�D �|
DEMACBx,X
��F�E�E �E(�D0�D8�G@NHVPDXS`AhEpAxG�I@Q
8G0A(B BBBID8C0A(B BBBd��
��F�E�E �E(�D0�D8�G@NHHPVXL`AhDpAxG�A�E�Q8A0A(B BBB	�
�	x$	�
��F�E�E �E(�D0�D8�G@NHVPDXS`AhEpAxG�I@Q
8G0A(B BBBID8C0A(B BBB,�	��E�D0{
AKh8L@c8A0$�	��QE�A�G CA@�	�DF�B�E �A(�D0�DP�
0A(A BBBB@<

�?F�E�B �D(�A0�DP�
0A(A BBBDL�
��F�B�B �B(�A0�D8�G�
8A0A(B BBBG\�
��YF�B�B �B(�A0�A8�J���H�Y�A�k
8A0A(B BBBG0��jE�N
MELP��F�B�A �A(�J�u�G�Y�A�U
(A ABBA0����E�A�G v
EAGxEAH�P��F�B�E �E(�D0�D8�DPv
8D0A(B BBBO( ���F�D�C �gAB$L���E�D�D �AAt`�*E�]�p��l��h��d�VD L
A���E�L
��E�L$
��HI<
��E�L(X
��)F�D�C �TAB4�
��;F�E�D �C(�G0I(O ABB�
��HI(�
��+F�D�D �UAB��HI��E�L(4��'F�D�C �RAB`��E�L4|��;F�E�D �C(�G0I(O ABB���E�L���E�L���E�L��E�L$�\E�?
D(D@��a�X
GXL [HP0p���F�A�A �G@�
 AABG�@�(�<�YL�A�D }AAH���p�%DY ���CJ�f
HFB� ��$4��rE�w
LM
KQ\�qDE
G0x|�NE�D�G T
FAEVFA`����F�B�B �B(�D0�D8�G@~
8A0A(B BBBEx
8E0A(B BBBB��]|$@�aR�k�@��M T��iE�_
LL
L(x��d[
APO ZHS,����E�bH L(K0I
K$� �RE�A�G AAA�X�aE�n
M(���E�D�G �
DAID� �qE�~
M`� �1E�N
EQ0�!��F�D�D �D0�
 AABK$��"�iE�Z
QX
HT��"�4E�Z
QC��"�L�"�$F�B�A �G(�G0b
(H DBBGD
(A ABBF8`�#��F�B�A �A(�D0�
(A ABBA�x$�(�t$��E�A�D@w
AAEH��$�EF�B�B �B(�D0�D8�G�r
8A0A(B BBBI(�&�#<<�&��E�D�G z
FAGD
CAHXDA8|H'��b�A�D �b
ABD���F �����'�$E�N(��'�rF�A�A �fABX(��F�E�B �D(�D0�s8J@THMPI0A
(A BBBDL(D EBB\h(�1\N�t�(�;F�E�D �D(�G0G
(D ABBFG
(D ABBHL
(D DBBHd
(D CBBI[8J@THNPI0D(A ABBD@)�
F�A�A �i(H0S(D F
CBEy
ABD$L+��E�D�D �AAt�+�4��+�sF�A�A �W
ABFDAEL��+��F�A�D �h
ABJF
ABGX
ABECAB4D,��E�A�G P
CAGk
HADHH�,��F�B�B �A(�C0�}8S@DHMPI0M
(A BBBD@�`-�1F�B�E �A(�D0�F@Y
0A(A BBBG�\.�4�X.�`F�E�D �D(�F0y(D ABBL$�.�F�A�A �x
JFHA
HKLa
ABLAIIt0/�	H�,/��F�B�D �A(�J0`
(A ABBC�(D ABBX��/��F�B�A �D(�G0[
(A ABBK
(C ABBEB8S@R8A0$02�RE�D�D DAXL2�LlX2��F�E�B �A(�A0�O
(C BBBIu
(A DBBB��3�<E�i
BK8��3�F�B�E �A(�A0��(D DDD4�4��F�F�A �D(�D0�(G ABB(P45��a�X
GPO bAe8|�5��E�vH L(K0IOH L(K0IG
G(�<6��E�D�D a
AAH��6�9\\4��6�sF�A�D �Z
ABHABH47��F�E�E �A(�A0�Q
(A BBBFN(A BBB\�T7��F�B�A �A(�D0D
(D ABBE^
(D ABBID(D DBB ��7�;J�\
�JCE�(8��E�G�G v
GEK80t8��F�B�A �A(�Fpt
(A ABBA4l�8�kF�B�A �H(�D0L(D ABB�9�HI(�9�HF�D�A �lJB4�,9�\B�A�G �`
ABChAB0 T9��B�A�D �G`�
 AABF8T:��F�E�A �A(�GP|
(A ABBA@�t:��B�G�B �A(�A0�G�	\
0A(A BBBH4��;��A�H�G z
AAD`
AAF<<��F�A�A �G0�8K@X8D0�
 AABALx=�`t=�QE�r
IH|�=��B�G�B �D(�K0�i
(C BBBDs(I IBBX�>��F�I�B �D(�I0�D@i
0A(A BBBEC
0H(C EBBI($�?��d[
APO ZHS(P@��E�N�GPq
AACL|�@�F�B�B �B(�A0�A8�J�
8A0A(B BBBE(��C��E�D�G@C
AAK(��C�oE�D�D Q
AAH@$ @D��F�E�B �A(�A0�Fp�
0A(A BBBA4h �D�cF�D�D �y
CBDF
ADA� �D�9\\� E�� E�,� E��F�A�A ��
ABD4!�E�gF�D�H �\(M0c(D AAB(L!�E��a�X
GXL cAex!\F�JE�t
OA$�!�F�kE�A�G ZAAH�!�F��F�B�A �D(�D0o
(A ABBJD(D ABB<"8G��E�D�G V
AAHD
QAJnADHL"�G�]F�B�E �E(�D0�C8�G`�
8A0A(B BBBE<�"�H��E�A�G I
AAHD
QAJOAD�"<I�fE�z
QQ�"�I�HKH#�I��F�B�B �B(�K0�A8�DP0
8A0A(B BBBJX`#L�E�D�D m(|0B8B@BHBPM {(J0a(D Q(J0a(F �
IDN�#�O�)$�#�O�<A�D�D mDA�#�O�$$P�KA�D�G hHJ4$0P�0H$,P��F�D�D �G0]
 AABJzPLRx��� �P$�P�m�TF�B�B �A(�A0�
(A BBBLA
(A BBBH(zPLRx��� �0�����0H��+TL��Q�	TB�B�B �B(�D0�J8�D@�
8D0A(B BBBE(zPLRx�� �@������0����S@�%�Q��F�D�D �J@gHWPBXB`N@n
 AABF@&xR�B�H�B �A(�A0�D@�
0A(A BBBIP&TS�4Q�Q
FG�p&tS�/A�i,�&�S��Z�A�A �\CBC���0�&�S��F�D�D �G0j
 AABE�&�T��\eW('U��d[
APO ZHS$8'|U��E�D�D �AA|`'$V�F�E�E �A(�D0�C8J@SHMPI0A
(A BBBEQ
(A BBBHs
(A BBBFQ(A BBB�'�V�KE�c
HZ(�V�KE�c
HZ ($W�5E�h<(HW�5E�h X(lW�
E�J��
AF|(XX��(TX�!L�(pX��F�B�A �D(�G0�
(D ABBLD
(Q ABBF�( Y�	)Y�	0)Y��E�A�G s
DAKYCA8P)�Y��F�A�D �@
HDI

ABK�)�[�	�)�[�AY�g,�)(\��E�C
M������
H�)�]�T*^��F�A�A �K0r8\@I8H0G8H@W8H0G8H@X8B0T
 AABA0X*�^�jF�D�D �G0K
 AABA�*�^�	�*�^�)HQ
GD�*�^�A�M
BE$�*�^�eQ�^
AM
�KV�+,_�Q�r
�ML(+�_�tF�B�L �B(�A0�A8�D��
8A0A(B BBBE x+�a��E�Gp�
AA<�+hb��F�B�E �D(�C0�E
(A BBBH�+�b��+�b�MH,�b�zF�E�D �D(�L0~
(A ABBED(D ABBP,$c�VE�n
E]Hp,dc�rF�E�D �D(�G@n
(A ABBB�HJPTXN`I@�,�e��,�e��,�e��,�e�-�e�( -�e��E�A�G l
AAEL-f�(`-$f��a�X
GXL cAe0�-�f��F�D�A �DP�
 AABA�-Tg�QE�F8�-�g��F�B�A �A(�G0`
(D ABBFH.h��F�B�A �A(�D0B
(D ABBG|(Q ABBd.�h�x.�h�"�.�h�(�.�h�E�D�D �
IDI(�.�i�dE�D�D i
AAH$�.�i�`E�D�D GCD8 /j�B�D�D �F(F0v(A j
AEK\/�j�`p/�j�9F�H�B �E(�D0�A8�D@C
8A0A(B BBBE�8A0A(B BBB(�/�k��a�X
GXL cAe0Tl�IE�{0�l�IE�{H80�l��F�E�E �B(�A0�A8�FpL
8A0A(B BBBH@�0n��F�B�E �D(�A0�GPu
0A(A BBBJ@�0�n��F�B�E �D(�A0�GPu
0A(A BBBJ$1Xo�GE�A�G nFAX41�o��F�E�B �D(�D0�s8J@THMPI0A
(A BBBDL(D EBBT�1�o��F�E�D �D(�F0q8J@THMPI(A ABBD0R(A ABB �1,p�iE�t
WX42xp��F�A�D �U
EBIrEB,D2�p��F�A�A �u
ABH(t2`q��a�X
GXL [HP$�2�q�VE�A�G EAAX�2r�F�B�A �A(�G0v
(C CBBG{
(C ABBEn(C ABB($3�r��F�A�D �pABP34s�HKl38s��34s�<H�3`s��F�A�D �l(P0G8L@I(I0I8H@Q A
AHH �3v�A��
AN
B,4w�A�x
G\
DX
HX
HX44�w��F�E�D �A(�G0y
(C ABBHn
(A ABBDY(C ABBX�4Dx��F�E�D �A(�G0y
(C ABBHb
(D DBBJY(C ABB,�4�x��E�D0{
AK`8O@{8A05hy�!E�[485|y��F�D�D �[
HKL�AB4p5$z��F�D�D �[
HKL�AB$�5�z�E�D�D nAA�5${�LE��5X{�HK6`{�QE�n
EX$6�{�QE�n
EX�D6�{�F�E�D �D(�G0B
(G ABBHV
(D ABBI
(D ABBHO
(D ABBH�8J@RHNPI(A ABBD�6`}�,F�A�D �d
HDEO(M0Q(A W
HEN87H~��F�A�D �`
HDIC
HEH�X7�~��F�B�E �D(�D0�N8J@THNPI0A
(A BBBH`
(A BBBIq
(D EBBJI
(D EBBJD�7(��F�A�A ��
ABEZ
ABC�
ABKT$8���E�A�G Z
DALK
JAJH
AAFN
DAEdAD,|8����E�D�D �
AAF�8���8$���8 ��(�8���E�hf B(B0IG
I(9����a�X
GXL cAe$@9D��vE�D�D eAA8h9����F�B�E �D(�D0��(A BBB�9��HK�9���9���9 ���9(��0E�S
HK`:8���B�E�E �D(�D0��
(A BBBA\
(A BBBE\
(A BBBE(�:d��/B�D�G �RDGH�:h���B�B�D �D(�D0H
(D ABBG\(D ABB`�:���_B�B�B �B(�A0�A8�DP}
8A0A(B BBBK�
8A0A(B BBBJH\;����B�B�D �D(�D0K
(D ABBD\(D ABB,�;���B�A�F �p
ABLh�;\��R�B�E �E(�D0�D8�GP�
8E�0A�(B� B�B�B�JG
8A0A(B BBBG(D<���d[
APO ZHSp<���9\\ �<���iZ�m
�IPH��<���	(�<��F�A�A ��AB0�<،�GE�H�G V
JFFDAA$ =��JE�A�G0zAA$H=��KE�A�G0{AAHp=D��kF�B�K �A(�G0a
(Q ABBND(A ABB@�=h���F�L�N �j
ABKA
ABDAHK>��!E�[>���#E�]48>��EF�E�D �D(�L0[(A ABB@p>$���F�B�B �D(�A0�D@9
0A(A BBBD�>���HNL�>���qF�B�B �B(�A0�A8�D�[
8A0A(B BBBIH?���8F�B�G �B(�A0�A8�DP8A0D(B BBB@h?����F�E�E �A(�A0�JPS
0A(A BBBI�?H��@�?D���E�pP D(B0B8B@BHBPIH L(I0QG
G$@��1E�D�G TGA,@��&HW`D@ ���B�B�B �B(�A0�A8�G� I�!G�!H�!N�!D�!_
8A0A(B BBBD(�@���AJ�A�G ]A�A�H�@�"B�E�A �A(�G0�
(A ABBFt(D ABB A���9HV
BX@AԖ�(TAЖ��a�X
GXL [HP`�AD��VF�E�E �B(�D0�A8�Dp
8A0A(B BBBAD8D0A(B BBB8�A@���F�E�D �A(�D@�
(A ABBK B��*E�]<B��RE�LXB\��;E�p<tB����E�C�G s
CAJD
QAJDOA�BЙ�E�W�Bԙ�0E�S
HKh�B��9F�B�B �D(�A0�D@L
0C(A BBBGD
0A(A BBBD�0D(A BBBD\C����F�E�E �E(�A0�D8�DP�8D0A(B BBB8�C@��^B�B�E �D(�D0�E(A BBB@�Cd��0B�B�B �F(�C0�Fp�
0A(A BBBGP$DP���F�B�B �B(�G0�D8�DPKXG`VXDPg8D0A(B BBBPxD����F�B�B �B(�G0�A8�G@VHGPVHD@N8D0A(B BBBL�D(��.B�B�B �E(�A0�A8�D��
8A0A(B BBBC\E��aF�E�B �B(�A0�A8�D���N�R�A�H
8A0A(B BBBJ\|E��'F�B�B �B(�A0�D8�D��M�R�D�C
8A0A(B BBBF�E��_D U
A`�E,��AF�B�B �D(�D0�D@^H^PMHA@D
0A(A BBBD�
0D(A BBBIl\F��SF�E�B �E(�D0�D8�GPsXZ`NXAPD
8A0A(B BBBE�
8D0A(B BBBM(�F���a�X
GPO bAep�F����F�B�B �B(�A0�A8�GP�
8C0A(B BBBJ8XF`NhOpKPg
8D0A(B BBBN(lG���F�D�D ��AEH�G���lF�E�E �D(�D0�l
(C BBBCP(A BBBH�G���lF�E�E �D(�D0�l
(C BBBCP(A BBBH0H��lF�E�E �D(�D0�l
(C BBBCP(A BBBH|H���F�B�E �D(�K0�j
(A BBBC�(A BBB�H���HK�H���(�H����E�A�G }
AAD($I ���a�X
GXL [HPHPI����F�B�B �E(�A0�C8�FPo
8C0A(B BBBDH�I���F�E�E �E(�D0�A8�DP�
8D0A(B BBBF�I|��E�T$J���EE�D�G rAA$,J���mE�D�D \AATJ��HKlJ���(�J���E�A�G n
AAC8�Jh���F�B�D �C(�G`�
(A ABBAH�J��uF�B�E �E(�A0�A8�GpJ
8A0A(B BBBAH4K ��uF�B�E �E(�A0�A8�GpJ
8A0A(B BBBA@�KT���F�D�D �P
CBER
IEHTFB,�K���a�X
GXL [HP�Kp��(L|��E�nF FDj
K(4L`���a�X
GXL cAe$`L��ZE�A�G IAAT�L���F�B�A �D(�Dp%xV�F�F�F�E�Ips
(A ABBKh�LĴ��F�E�D �D(�F08J@SHMPI(A ABBG0L
(D ABBKN
(A ABBLLMH��iE�t
WXLlM���F�B�B �B(�D0�A8�M�G
8A0A(B BBBA$�Mȸ��E�A�G }AA4�M@���F�E�A �A(�G0�(J DII4Nع��F�M�F �G(A0K(H DCB$TN0��LE�YI QANH|NX��F�A�A �](L0K8B@I H(L0I8B@Q o
IEL�N,��2E�U0�NP��sE�D�G |
AAJDQA0O����E�D�G a
AAE^DA(LO���a�X
GPO bAeTxO����F�E�D �D(�F0q8J@THMPI(A ABBD0H(D AFBh�O���F�E�B �D(�D0�s8J@THMPI0A
(A BBBDZ
(A BBBGL(A BBB$<PH��YE�A�G HAAdP���HK$|P���IE�D�G lGA�P���D@,���|�(F�H�E �A(�D0�D@�
0A(A BBBD �,���1�(A@�����H�,�����(F�A�A �i
ABLA
HKEA
ABA$zPLRx�` � ���,'��'(�Q��'E�a�Q���KQ�[
DZ@�Q(���E�H�G a
AAII(N0P(F N
KAI00R����F�A�A �D`�
 AABH(dR����O�A�G@{
AAD�R��oE�q
b4�RX���F�A�K �k
ABHwAB�R��(�R��3F�D�G �RDG($S���E�D�J@V
AAE@PSt���F�A�A �e
DEJV
CBEVCB�S����S���.HI I(E0N8�S��F�B�D �A(�G`�
(A ABBEHT���IF�B�B �B(�A0�A8�DP�
8D0A(B BBBF8PT���SF�I�A �A(�DP�
(A ABBI �T��eE�G0R
AA�TT��E�L$�TX��CA�A�D wDA(�T���NF�G�A �qFD@ U����B�A�D �{(G0{(D J
DBHAAB4dU��wB�A�A �M
ABD[AB(�UX���d[
APO ZHSP�U����F�B�B �B(�A0�D8�G@DHJPmHD@|8A0A(B BBB$VX��1E�D�G TGADVp��<E�qT`V����F�E�D �D(�F0q8J@THMPI(A ABBD0H(D ABB8�V���E��H L(K0IOH L(K0IG
D�V���#E�]DW���B�A�D ��
DBJd
HEOA
ABAXW���8lW����F�B�A �A(�F0m
(D ABBJ�W���)HYd�W���B�B�B �E(�D0�A8�G@�
8C0A(B BBBEx
8A0A(B BBBA8(X���?F�B�A �A(�G0(G CBB$dX���GE�D�G mDA�X���$�X���1E�D�G TGA�X���9\\�X���X���#E�X<Y���F�B�A �A(�G0A8P@DHBPI0U8P@DHBPI0U8P@DHBPI0g8H@LHHPAXD`BhBpI0U8P@DHBPI0U8P@DHBPI0H8L@IHBPBXB`Q8H@LHIPQ0U8P@DHBPI0U8G@KHKPBXB`I8H@LHIPQ0T8H@KHKPBXB`I0T8P@IHBPI8H@LHIPQ0T8H@KHKPI0T8H@KHKPO(A ABBH0(TZ����d[
APO ZHS�Z$���Z0��
�Z,��
�Z(��
�Z$��
�Z ��
�Z��
[��
 [��4[ ��H[,��\[8��p[D���[P��KH0}
A�[����[����[����[���'HN P�[���\��� \���KH }
A<\��P\��IH {
Al\H��IH {
A�\|��E�Y�\���9E�s�\���`E�V�\���$A�^�\���1N�]�P] ��E�A�G �(G0I8H@DHBPBXB`I H(I0I8B@WAAG Hh]����F�J�E �D(�D0�W8N@V8A0A
(A BBBG0�]����F�D�A �G@�
 AABA8�],��hF�E�E �D(�I0�(A BBB$^`��oH0q
G@^���2GRE SD`^����B�B�B �B(�A0�A8�G@x8D0A(B BBBL�^,���F�D�B �B(�A0�A8�G�
8A0A(B BBBF4�^���UF�G�K �l
ABIAABd0_����B�B�G �E(�D0�G8�JpJ
8A0A(B BBBG�xM�[xApexO�SxDp`�_����R�E�D �D(�G0]
(J� D�E�B�Hg
(K� D�B�B�FD(A ABB@�_x��!R�B�B �D(�D0�
�(D� B�B�B�I(@`d���a�X
GXL cAe4l`���mF�D�D �K
DBIAABX�` ��6F�E�E �E(�A0�D8�Gp�xF�_xDp]
8A0A(B BBBGHa��F�B�B �B(�A0�D8�D��
8A0A(B BBBALLa����F�E�D �D(�G0B
(F KBBOK
(J ABBFL�a8��hF�B�E �B(�D0�D8�G�%
8A0A(B BBBC8�aX���B�P�A �A(�G��
(A ABBE(b���8E�r(Db ���E��J aD�
Kpb���/E�i�b���/E�i�b���HN(�b���iA�^A lAE
FP\�b(��?B�B�B �B(�A0�A8�G�U
8A0A(B BBBH.�L�U�F�Lc��`c��IE�S
Hd�cD��EA��cx��/A�i�c���PI�r
EN@�c����F�B�B �D(�D0�D@~
0A(A BBBADd(���A�P�R(B0D8I@AHDPIXA`BhNpY ZDA ddp���A�I M
AH�d���HN(�d����d[
APO ZHS��dX��7F�E�D �D(�G0}
(D ABBHR
(D DBBJJ
(D ABBMK
(D ABBLk8J@THNPI(A ABBXe��$E�N<te ��E�C�G 
HDFD
QAEAAA\�e`��aF�E�E �D(�C0��
(E BBBHy
(H DIBHA(A BBBHfp��|	F�B�B �B(�D0�A8�Gp
8A0A(B BBBG(`f����E��
HQ
OQ
O(�fx��<E��
LQ
GQ
O(�f���A�A�D@�
AAI�fp��(�fl��QI�C�G fH�D�0$g���cE�C�G j
FAHDQA$Xg��]A�D�G @HD4�g��QM�D�G Z
G�A�FDAAJ��0�g<��kE�D�G j
GANDQA$�gx��]A�D�G @HD4h���QM�D�G Z
G�A�FDAAJ�ƴLh���F�B�E �D(�D0�_
(H EBBKb
(D IEIKI
(D EBBJI
(D DBBKI
(D EBBJI
(D EBBJ[8J@RHNPI0A(A BBB0i��kE�D�G j
GANDQA(8i��E�D�D �
AAHHdi���B�N�I �A(�A0�K8A@I8A0x
(A BBBH �i4��E�G v
AE�i��YE�p
KX,|E����E�A�G u
CAA zPLRx�MG � ��(����`j�;E�qdF8��dB�B�B �B(�A0�A8�DP�
8D0A(B BBBOT
8A0A(B BBBA(zPLRx��F �P������0ُ�<	L�F\�
B�B�B �B(�A0�A8�D@�
8D0A(B BBBN�E���&�(�k��E�jf B(B0IG
G�kh��E�q
J(�k���d[
APO ZHS<�GL��UF�E�A �D(�G@�
(A ABBA$zPLRx�)E �@����,ێ��
�l��E�H
C(�l��	E�A�D �
GHE<\H��V�
B�B�A �D(�G0�
(A ABBJ�HM��E�
A0����(4m���F�A�K ��EB`mt	�\\xm�	�QE�n
EX4�m�	��F�D�A �^
DENVCB0�md
�vE�D�G {
ADHYCAn�
�sE�iD n��F�B�A �A(�G0�8G@a8F0�
(A ABBJ4hn��oB�D�H �]
DBOdDB$�n��SE�H�J wCA�n
�	(�n
��a�X
GXL [HPHo�
��F�E�B �B(�A0�A8�G@�
8D0A(B BBBH$To ��E�A�G lFA$|o��ZE�A�G EFA$�o��YE�A�G DFA$�o��YE�A�G DFA�o0�(E�WhpD��F�E�E �A(�D0�{8J@SHMPI0A
(A BBBEH
(D BBBNc(A BBBl|p���F�E�B �D(�D0�s8J@THMPI0A
(A BBBDD
(A BBBEL(A BBB(�p8��E�D�D �
AAEq��(E�b84q���F�A�A ��
DBNH
HDL$pqd�@E�H�J _DA<�q|��E�D�G \
AABD
CAHTFA �q��gTTT O(H0S�q(�r$�$r �yE�]
FDr��#E�X$`r��DA�A�I mFA(�r���a�X
GXL [HP8�r0�F�B�A �D(�D0�
(D IBBJ8�r��F�B�A �A(�G�y
(A ABBHP,s���F�B�A �D(�D`n
(A ABBK^hVpFxF�F�E�I`�s$�^E�n
M]X�sd�WF�B�B �B(�D0�D8�DpS
8A0A(B BBBKcxH�TxAp8�sh�F�B�A �A(�J�V
(A ABBH@8tL��F�B�B �G(�D0�GP�
0A(A BBBDD|t��\F�B�B �D(�D0�GP
0A(A BBBA�t��PE�Jx�t���E�A�G }(O0K8B@I G(O0I8B@Q(H0O8I@AHBPBXB`Q(H0N8I@BHBPQ(H0O8I@WAAJ L\u(��B�B�A �A(�D0�
(A DBBK�
(A ABBF$�u��NE�D�G yCAD�u��B�B�A �A(�G@oHZPIHA@T
(A ABBFLv���B�B�A �A(�G�8�Z�I�A�T
(A ABBEdlv!�[F�B�B �B(�A0�D8�DPR
8C0A(B BBBE�XH`ZXAP�XM`VXAP(�v%��d[
APO ZHSTwt%��F�E�D �D(�F0q8N@UHMPI(A ABBG0G(D ABB$Xw�%�ZE�D�D IAA�w&�HK\�w&��F�B�B �B(�A0�A8�G�N
8A0A(B BBBC��Z�K�A�(�w�)�gF�A�A �RAE<$x�)��E�C�G l
AACD
OHE\HDXdx *��F�E�B �D(�D0�s8N@UHMPI0A
(A BBBGL(D DBB\�xt*��F�E�E �D(�D0�W
(A BBBJA
(O IBBKu(A BBB y+�VE�m
F]��$��.��=��
������:��
��34�HA��!��������������
*��
1T�t%��
��
8]�y��
��5-C�Z�m�u��������'��X��*
#3��$�-�;1�u�}������
��/�����	
��6�1�S�^�o��$����������
/(GNU�p���0��� bEɃ�������(�0�/�@�z�X�\���t�����������˄Ԅ����)�0�K�T�t�؆r����� ���@8������������ƅ܅��@����������eE�eE���eE��������ʏ�hE�gEfE������fE@fEt�`fE����ӈ߈@gEgE�fE�����;������������H����������gEh�`hE hE�gE�����;�������?��������������hEX�iE�����;������������ ���&��Q��m�mnn$n5nCnQn_nvn�n�n�n�n�n�noo*o;oQoeovo�n�o�o�o/p�[
h�bEbE���o`0v�

[p�oE�]�h��
	���o���o�����o�o�����orlE\ \0\@\P\`\p\�\�\�\�\�\�\�\�\]] ]0]@]P]`]p]�]�]�]�]�]�]�]�]^^ ^0^@^P^`^p^�^�^�^�^�^�^�^�^__ _0_@_P_`_p_�_�_�_�_�_�_�_�_`` `0`@`P```p`�`�`�`�`�`�`�`�`aa a0a@aPa`apa�a�a�a�a�a�a�a�abb b0b@bPb`bpb�b�b�b�b�b�b�b�bcc c0c@cPc`cpc�c�c�c�c�c�c�c�cdd d0d@dPd`dpd�d�d�d�d�d�d�d�dee e0e@ePe`epe�e�e�e�e�e�e�e�eff f0f@fPf`fpf�f�f�f�f�f�f�f�fgg g0g@gPg`gpg�g�g�g�g�g�g�g�ghh h0h@hPh`hph�h�h�h�h�h�h�h�hii i0i@iPi`ipi�i�i�i�i�i�i�i�ijj j0j@jPj`jpj�j�j�j�j�j�j�j�jkk k0k@kPk`kpk�k�k�k�k�k�k�k�kll l0l@lPl`lpl�l�l�l�l�l�l�l�lmm m0m@mPm`mpm�m�m�m�m�m�m�m�mnn n0n@nPn`npn�n�n�n�n�n�n�n�noo o0o@oPo`opo�o�o�o�o�o�o�o�opp p0p@pPp`ppp�p�p�p�p�p�p�p�pqq q0q@qPq`qpq�q�q�q�q�q�q�q�qrr r0r@rPr`rpr�r�r�r�r�r�r�r�rss s0s@sPs`sps�s�s�s�s�s�s�s�stt t0t@tPt`tpt�t�t�t�t�t�t�t�tuu u0u@uPu`upu�u�u�u�u�u�u�u�uvv v0v@vPv`vpv�v�v�v�v�v�v�v�vww w0w@wPw`wpw�w�w�w�w�w�w�w�wxx x0x@xPx`xpx�x�x�x�x�x�x�x�xyy y0y@yPy`ypy�y�y�y�y�y�y�y�yzz z0z@zPz`zpz�z�z�z�z�z�z�z�z{{ {0{@{P{`{p{�{�{�{�{�{�{�{�{|| |0|@|P|`|p|�|�|�|�|�|�|�|�|}} }0}@}P}`}p}�}�}�}�}�}�}�}�}~~ ~0~@~P~`~p~�~�~�~�~�~�~�~�~ 0@P`p���������� �0�@�P�`�p�����������Ѐ���� �0�@�P�`�p�����������Ё���� �0�@�P�`�p�����������Ђ���� �0�@�P�`�p�����������Ѓ���� �0�@�P�`�p�����������Є���� �0�@�P�`�p�����������Ѕ���� �0�@�P�`�p�����������І���� �0�@�P�`�p�����������Ї���� �0�@�P�`�p�����������Ј���� �0�@�P�`�p�����������Љ���� �0�@�P�`�p�����������Њ���� �0�@�P�`�p�����������Ћ���� �0�@�P�`�p�����������Ќ���� �0�@�P�`�p�����������Ѝ���� �0�@�P�`�p�����������Ў���� �0�@�P�`�p�����������Џ���� �0�@�P�`�p�����������А���� �0�@�P�`�p�����������Б���� �0�@�P�`�p�����������В���� �0�@�P�`�p�����������Г���� �0�@�P�`�p�����������Д���� �0�@�P�`�p�����������Е���� �0�@�P�`�p�����������Ж���� �0�@�P�`�p�����������З���� �0�@�P�`�p�����������И���� �0�@�P�`�p�����������Й���� �0�@�P�`�p�������p�� �;���GA$3a1�[u�GA$3p1113P�loGA*GA$annobin gcc 8.5.0 20210514GA$plugin name: gcc-annobinGA$running gcc 8.5.0 20210514GA*GA*GA!
GA*FORTIFYGA+GLIBCXX_ASSERTIONSGA*GOW*�GA*cf_protectionGA+omit_frame_pointerGA+stack_clashGA!stack_realignGA$3p1113��f�GA*GA$annobin gcc 8.5.0 20210514GA$plugin name: gcc-annobinGA$running gcc 8.5.0 20210514GA*GA*GA!
GA*FORTIFYGA+GLIBCXX_ASSERTIONSGA*GOW*�GA*cf_protectionGA+omit_frame_pointerGA+stack_clashGA!stack_realign
GA*FORTIFY���oGA+GLIBCXX_ASSERTIONS
GA*FORTIFY�f�GA+GLIBCXX_ASSERTIONSlibgnome-shell.so-3.32.2-58.el8_10.x86_64.debug5�М�7zXZ�ִF!t/��e�w]?�E�h=��ڊ�2N�c���$�0�y�*��l���i��'�����������Q�E)���
zop�o�C�u��T�R�%�r�AxM�`?8�Û��|�sH6�ǁ�]��L�y�,��[�}r�ڝ1O�|�������2�)�.lp��rw�>-Ղo�6s���4D@|I��u��Tql�p�0�А3(4{[����aD� B���C���`��`�s٣d6A�E�}��x�By�0��@mL������8���f9���8�'|͑��q�_�C��HaX�lR3�R�'��c.vnK�$��c�M�sIg�d���v`�'�WUY,#��r�����>*�=��y:���L% w�A^�D��ϔeU�����16�В�R����U�[�S�ss�a|(is�;�Zn���k.�/�ֺ��_�{ϊ��=p� U(f��W1�Fv+t�l\b����n�X�fz�y'�	�����hv_�c�u��X��R����i�n�2t���s�*�~�h���G3tfe"<�F5�%L�X�yE�:16NY�B*��#a�/!73�ݥ:��s�S�e�V����?���O�jbQ���n���˞Q�F��ЦT�bj>b�	�鑌�7駧`w+��E�D�z�ަ6��,�_�R��"�u�%�4���R�?��˂[ϲ糶��0���2'���-�����s)2A�EU��8Q��W?����%B��2'����:y;{�ܮQ��]�4����� `��[<��x���v��������W�.$�:�\��Ok�{���Nz6>�.u۔l���-�)n�R�����D�4*�O�3g�v�]?�^I��`	�	Gk�T3>���>�����~6r�p�4�epXs<o�G��kGA �.����v��b�*@v0+ʠ��7E�.�̋V��i���S+��N{�����U��A)DZi�D3hq�MYլ��
��e�?62L���o��>U���붹W4��먏�1)Q]��~�Y�|�C�O��O��[���ٯֈry'*H�<?l�wJ�|$h����2�1�Xd��Y%���N�z��!䩘�U��N�⚠���+9�(�N��SG��*x0��E��h���fXWQG-"h��UԬDQ�;?Ǯ_����^���sC���{��<��>0�X�9�f�i�0="P<}��q��ۇ�Zl���v�dE�2��4{o�i����"t��(���v�:W.`�sfg
�0J,"x��~��ޔ*#�<�	���S6
O��g{��Q�{a16��y/�1�Uɿ��N��n#
�-��x���֙W�r��F1��@f��/De�+-)P}K�x��k�[���Fײ��LV�uonu�6��)�E��	Jh���*8�b
����8PnT&lʮ_q'��}�7�@D�y^�:�&XJ|2�Jp
�/��TL���>���KV�3�.�S���u<}ҵ�{�nQZ5�f�z(y��@6J����%z��<0�
XsFN��Q����.��G����i�e��R��=�n��v=�V^q����K���}�|�a�����[%��oH-���\�4������%�}��Ʋ#M�̕���

�"M���a�,�"7�g�ݡ��	52x�6���OT9��./����7x#���[��w�k?휤m�Q��
M:�t�����g�ώev@�Z����:+�.p��f�_KpD�A��E�ɯ��<d�X6w��z��~=-����p-����z�`���t��xh*�Γ"K��R�5QZ2S���1Q��]ċ]�������q���8��Tפ!w�iҝj���o��NQ X�Lp$�[��jzB9l���"0i#�=��Zņ�9�R��E��y�_���}��ϝ�|���z�>�IP3�`;��\��|�������{��U�x$C�
q����=�����m(����kbe� ��qR��ĥ�i'"���v�u�n��C����-���7zz���<oC�攔W���I��'ɮ 8�J�G-=��왔�N��{�jہ�L��<�_`��}�>�n��E�6�oG�M/�l���.JN�a���	ة��c�P�G�Q�V�#�my�u�x�o�g-4?_�VE�c�Ϸ�i�q�96������?�_>�9`�I��Rg��]��h�%&ݪjq���Q��1ra�M�}
� Ϯ%q-$7��*������V��.�����m�c�a�*�T�����r��U2Y&��E��>J�3s�F!�S�"'"��\D�m��퍊C��űw�4��z �	=��(����}���;n:�s�|�qh�9���s�dD��S����X��rO�\���V���?R�P�A Vڞ)��,2�=^��[/�Vd���ǧΉ����=�=�L(�pa�S��1ő�K0��y�F�p���������j�N��]���{TjC$V ����6�C+
�#�Z)��2+���C�Ə`jE��v{�[ի�T�	�k�����M�n�0o�9�z�]�[X��jUu�H^��-CʎK�������82{`\h(�[�9SGϝ1�2�19�����9 61{vG[��j�O>�D�%B?�~
�M
��=�tP�S.o8|HW[=��P�Kľʧu\a{��w �ʗ\�z���gf֋bYcʼn������wģ;!Z�@'S~r+C���ra8�7j�S���LB��"���;9r/]�hr�&�i^����ٸ-�Ǒ;�-�ͮ
��" ��UB����ϱ��*�j)�6S��1�FZ���� yt|�q"Mfj�*fN.N�n�T;�MR�%̵8�ĭV6���\����; d��Փ�"7����s᫢�J�Jf�.���A�W���`�'�2x@��z�+��1�����
�[��ZP'�ִa��6��e>�95FS�~�o�}��B3f��o�b_�D��%�9�Ľ�Q8��Wѫ3���	+���t��%�C�N�6�s{_UMg��F�.J��TOw�I�Bl�����(�:�(�+^b�x��a0�:nx�`��B��"x�:Yٺ	��:�
���Ǟu&�����|��N�>0���u8�c�4�����<�%-�����q������|p��d�oo�Eߍ�*̠�H_�
�1nT�Ll5\g���,���Κ"�����H\w	
�%c�Y�N�?���jN��#m��b.�(q(�aR��U	4��>p^�i����"׻5�!����*��Ƹ�г�?�d��25��e���>�Dz��R�J%�8���EI�.�g:E�������ט=O�u&0�)))p)�48H�^*�*P^�gxx/����F5E$� ?�QPm�W�oҠ��Br	3��U�����{���+$�����w��+�vݨ���M��]��qۯ�L��NB��Z��Q����m$6!/b��S����I���x�N�"0]�v���(�i	À	�Εt�ɏ�����@[8��%A3��m!��v譣�|b�଩:����q���I�/��kIwxa��3j�II9E7�Fj?XG�������n�:m�|��	��=(�B��T~�$�˰����>N0��"^�>�(v����D����L�T�HNWñp�	�i�:%����?�Q�—�X8�����?ㅦ!�U�b���&��K-f�&<�_|lx��#�>tv!b�
��#I�ʇ���uޗr�HA���f�U���'񔀜�C�L�X��E�8�8_𜛰L��F��|��>�l
�cp	���0Ө� �_g�95L����\��1O�8�)���"�,8dT��^n eC�}#ͼ�ːm��#��yY�?;B%�)Ҍ�cis�A�y"�2~�U��[k5�D�a�AF2�>Y�����]�YH����9�+WY.���k�$?�Ƚ�n�����n�̷W�D�$���@P&-����j��\r�ߛ��!�;�vu��Z�.�q�
�#y��X����ŏ _��N�l��hm|��Z�D�ԣ4M�#�3T�[���b���/��3�=�Bq��b}6
�:��X!*hsI�]N3�����AO�tO\G�_#��bQ!�8+��_�<O�.�Y�%��C̲�!E=P
���<��IJXu$�zm���J��L�".��)}�&h��Vh&2��=s�Y��?���E�[�x%>��j��~65e�QT31��ֲM�yg�e��G�-2@2���0�;r�(pw�j$>�fҌ�
�XK�i�Ң{$7��j�5ʲk�
ݫ�c�߮)Ec�����J�k�z���,������&E�Z<�yV���ZqȢ	3�`�O�H~�8���j�9��pS@��R��n��=���_;�ϱ��"��{6�M,�,��[i���R0�����[�|:k�yƧ�8\���+$H!٧�j�|&�f���H
_�&GXlֿ'��B��5�>;>3c?[f��#�>����5SZ ����
�3�I�f�{���ܫ"�8���W�И���աK3�Φ�\��m�=��~R�v}LY�su��h;�;�</����Tk/�xJ��È�1�EM'��VOF�Kw�t��i��R�*�|��T��-q�kJ����ʌ�/Vգ�s�����y��쯶�x}aHZ?��)�'0˄3n
B'y��!�1�C���I���ҍ��.���C��-Ops�^����*��!�•E�kp\�TC��@�=Hk�#��f'z��.w�bdus�5O��b�?v�m�����dk'���۸�/�W{R�2`�d���o����&j �J�o[[e����8��5���&K���.7+W$؀���"� �k�Q�O�׉]����=2�雦����º��U�\�R�z*�;(��8E�A?���+k\� ?4�f������yy���%�x�����1:�7�%��i��~�F�a@]{חr�m"C��9���F�8sJC��a�e���؊*���zȽF���5�ƄgeHD�t5#�I�
����c`��Z9�[���5�,=��rk�>2�b�"�	�e!�e��nwD�R�LHײ�j
�ܭ�+�΋s�Rk�j6��$E�i"r�C�գ�O���ID+i5�F�TW�CQ�X5��]j,=�1<���1�u��)�����Ʊ�g�YZ.shstrtab.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.sec.text.fini.rodata.gresource.shell_js_resources.eh_frame_hdr.eh_frame.gcc_except_table.note.gnu.property.init_array.fini_array.data.rel.ro.dynamic.got.data.bss.gnu.build.attributes.gnu_debuglink.gnu_debugdata88$���o``0(�
�
�k00v0v[p8���o�����E���o�����Th�h��
^B���]h�[�[c\\�>n�����>wP�P��}h�h�
������L �p�p�� �`�$`�$����$��$@y�(X%(X%���Y%�Y% �bEb%�bEb%� bE b%�	 lEl%��oE�o%P �E�%� ��E��%� $x����%l:,�%4I`�%��%X