%PDF-1.4 %Óëéá 1 0 obj <> endobj 3 0 obj <> endobj 4 0 obj < AnonSec Shell
AnonSec Shell
Server IP : 212.252.79.165  /  Your IP : 216.73.216.24   [ Reverse IP ]
Web Server : Apache
System : Linux 212-252-79-165.cprapid.com 5.15.0-153-generic #163-Ubuntu SMP Thu Aug 7 16:37:18 UTC 2025 x86_64
User : cehaburo ( 1001)
PHP Version : 8.1.33
Disable Function : exec,passthru,shell_exec,system
Domains : 48 Domains
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : ON  |  Sudo : ON  |  Pkexec : ON
Directory :  /lib/gnome-shell/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ HOME ]     [ BACKUP SHELL ]     [ JUMPING ]     [ MASS DEFACE ]     [ SCAN ROOT ]     [ SYMLINK ]     

Current File : /lib/gnome-shell/libgnome-shell.so
ELF>@@�)@8@�c�cppp�f�f���xj%xj%�T)�d)�d),�1�`)�p)�p)�����  ���$$S�td���  P�td��(��(��(��Q�tdR�td�T)�d)�d)�+�+GNU�GNU��x.4�ٙ�y���!C|;z i��h�a�Pp# �*�B�XQ��
BB��DRL��<G
� �� d@AH�@
B��
` 44T�Xhh @�R,a��*�P�e�#�AB�� 
�Hg	@�U�Y	`k` � B0!#(,����$�6�E!�#@!�	�@20�b�@!$R�3T! x4�T�_9DPB|� @!W�f)��z{|}~�������������������������������������������������������������������������������������������������������	
!"#$&'()*+,-./0123456789:<=?@ABDEFGIKLNOPQRTUWXYZ[\]^_`abdefhijklmopqrtuvxyz{|}~��������������������S��O�>��d&��{eQ�%7�tG@��.G�g��op���Ut�\z+C��c,6YB7[H'���w\��x�C�|�7�.ӌ���~�xO�q��Ji�ϵ�o��)��^Q����哇c�c�W���>!wD�=�C�	Sp�"e��,��a^�Ǣަ@�3_g��ą	1'a9;���x(�����Ӛ�ܦ)(�"y�E�|_�T%�n?A�SQ�˘
]�_:�ͦ4?B6A�ß�Ԁ����07�I'��-%TF�INu7���(=��,�t��Bhv�>���)�JzSo��ߐ�=�J���C
�-:$��� |���C����9a87|j:�q�}Ռ$��$�����v��M�Q(ؒ���������)�ni��X�~q˲��u��N�5��#�w�Y�w��臿F�䣢���iR;�ͅqy�X����ë6���v�ϩKG��$s��QA�s�	�����Ǥ�֥j�B�
��{h͕�!�q�!�a5@����E���M�I��0�%ð/�(����;���<���� =�\k�����8R�q��sg}>�֢	�Ɛ��u��&�f���l�����ku�vy�߂��B���P��y�	�kJ�ϣ��{Ts���v_u��kJ�	`�Z�kJ���4N��«�7.���x�;��H�~u��9BmK�G�D�w��)�u?�l�ߝ%��L�W����<�[���/��#�x/�����I�%��>�k.�g�VB�;�T]
#�f�Q���Y�9Oٵ��u��pi�V�T�7¨}�=*��X�.���Q�U@
�C���ל����,�̢��<z�w���|��3��4���
�k�	��C��w�c&aMC>m����9q���n����9uJ�m�4����L��)��.��3�[\�9}b��I7�_	0jA��'Z����	;�e���^��{F]-�,/�^(��(��W�9�2��&8�rԨ���i�j_��r�����]�nn�'����p��˛pE~�gC|x���.;�m�����ݠ�a��0
)�!X��}�҄4�?�A]]�[��=tf���77)^(?�_��v�UW�*@y�N>/�϶ܪ@���"����Gk�Z9��!�LtՌ��~u�.�
Y�SG;X,Yk�9.�C�+A+t�Q_@LT�_�i6�:�3�/)q	/�I�[�\�W��`d�;�kou�
�.R�\��:�|&.*: _
)�4��S`9�5�R�n5|T�,�P��5�T8
oYvI�2�=�8rb�H}o{UQ!'�R�R��]�g}:�]'�@4>W�X;aS�<Do�_c]�^C�]wv�jn��7uK5�Q�c"�4��	Z�=a8�	�@�;�#U�'b;�XU[�?�5^�V�J�QTM:�P��@

h\�.DG*�%OY�K�o�W�k7)�@F"P5P/1�dr�<�W~Ki;�@n'k�4Fq�_2s�7\jqY��<�m�*�>�]Tt��b�9�KF]�v p�T�-.
�6^!^d�^K�n�s�5�1�$�F�p`(e#.Q<	hX�p�r$�
tx9�4\� �9�!=L�SEa�%�-�0c%�0R�r!�&w8�o!d�,�E�_/-{�m�<�D�)0h28&1a�#�q�%�ihQObZf��1
s��$HW�(Xo �B�!SWh<�c��-i>�2�	[���[�L=S@OmXlGUZ�Yg�>U�;�1�0�C"%�:;5�5@Z62qI9�QA<�)�X1�9�
�t[	�/�E�ua4V{;/`~�_f�,�n��
\L�n,#�&���`*.�:Zbn7�(j=� X
"!"]E1us��)l�.0C�;�B?
%`GD��t� bd:��\�[�C.%6p@6<�e�M})�U'�C�B�C'�s��7	3�N0~Wa:=R^��Z�t-]�'z]�sjp,�A)8�[�Hd9�&H�Z|QH.9m����6�xR�fYq~]�u�
\69L�7�08(9��?��C�
�>oqt�c�V�&��\/�UY�;�j"�,��O�G'
\~�%�3%
�GA�3�+)6�n.�45�!�	#nH:\MB�;�EX0�MIX7[�S�^4>(��tj�+�!�o]*�?
��O�,.A��i�1Q\�u5�A�-�+s�8�_:l�6�3�\�Y1!^dA�X>,�m}2�+�7@_�S�,�AZW�x'%��0v��C�O��fc�>�o�i*]VX�O�S6�2-:}/t$�xgP��V,5A�i�f��o�
=d�]`�4�
" O=�m!M�AQs��Zwnj[�
�aR�r!lK_�[&��u�
&>��_�65#q_�
9(+�s;T�;
Lq	��EEg;J>7�9�O�<k?�`�r6tM*�n�8V:[�.%,o�H�0�:�K�n�2Q�>�8	#�IPp�R?8�Ov?-vZ�
D�8B&P�pRV�m��t�@	�R����]��^�r��D�4l��7�Kdjb�ODI)�q�?R?�_�+[_>eT�'uX`t
._�9�/|�8�"�Yg=3<�$�7�$M$
4wlkB%^u�X�NC�V�)�r"	�T�	R�o0u�2q�
�YiD�%K/wl�6�=ha�]*�9"W�d u3?67{�q%�$V8vGmU�1�;�nE� �6F��5&U?@�=�7�qWH�=-u�*l`�F($�Yb-rF�8�d��UU>�3FK�VnK
�^8&[;q�l�u]Fuj�'��#6Y�T�\� 6#VimWr?�, �<�QaBz�?�[_KR�Z�O<�i�U
t,j^r<�<�?`)3/mV�"�q�Aj.2;q�I�+<fo�S�.V�Ep�Y<+`y�hP�Pa�w�"��
iwB����P�/���C��>f��
�a�ykp���e�@�pp�Y�p�K�m46�
$�_YGB��|�Y�@$�`�vw"T�/�]h��b�{3cPK1�e����+#�h�K�a�y.�B0�"m�Y�KP�yfЏ
H3P�a4���4"W	���l`�Q�F��Y�/��UOP�f�
gOPY�H0��@P�F!PS#��1��-����l��r�������(pn��# _f�KpY������O4�G`��0o�M�
�DP���h��f�E*/�0��c�}Y�*�vY� -+�*�v�N��t�e�"XY�`�u�QP$YPoi`	�*g�
�I �u�a�x��*`vf��-i��I�
P�g �����AP�	�H@��!T&EU�3j'F���%�m�lP Udi�YW2��hH��Y�
�+b�yQH#�[�RO0��,V8�Oi�)ps�W5��2��_)Bp�	YU04j�e0�YAE��k@��((�l\K@��h0��c��Y�#�\Y�l�\�D���J��"E���I��PoN@<�Ly`E �]K���`e�)PtpZ
�oQ�$Y�(�o�jrp���P�#Y!S0*Y�B@�	FP���r��V=`�3p���d�"��%*j0P��;N���kr#p\YNF0�"&O��?Q`&�(�lWi�~a�w3UI��Y`�,#6G��4��r}(n[9r�iJ��P=k@���s �)g(��hp�'?JP�k�0P(J�Q+Px
K"W��j��!Np<iP�I�f�
M+py!s��/0`�aw�2�h,o0��mC���OS�,��
d|�"�4��;��M��b`{F�e��
D(Pm��LpR�N�8t�!pU<[c}�P6�T@/��b@|3xbP{	�h����EYK���F`�+�`3h�p���P
+�j��No�^CP�)m!�S��k�Y%3�[>
pomI�907Y�I��	ht����N�<�`ik����(@oTvJ`�%/`,+�"�Y��G��QfS�,��,+�A`�	�0,#c�|@G��Y640���EP�`h)�qY�J��'ce��Y�M@,�#�_9�M�D;H����XDph0��S�.Q0���,p�Q R%t*�u��D��scg�
�Y`H�h��[3`� �l���pc }e$I`�VJ���5c�|3o+�y�g@��g`�c�o�!@Tr]P1h1���b�{F�a x��G���@FY&pY;e����"�Y9�E@��e�9�J��E��2�+K@K/@&I�P-YM�Y2!@Sh��XG�Y�H�Bh��K�e��%h"�W�l`�v;M0�o 
#��PhL@*�.`���G�QX>���o__gmon_start___ITM_deregisterTMCloneTable_ITM_registerTMCloneTable__cxa_finalizeg_value_unsetg_strv_lengthg_strcmp0g_assertion_message_exprg_strv_get_typeg_type_nameg_logg_value_get_ucharg_value_get_booleang_value_get_intg_value_get_uintg_value_get_int64g_value_get_uint64g_value_get_doubleg_value_get_stringg_value_get_variantg_variant_equalg_value_get_boxedg_intern_static_stringg_type_register_static_simpleg_dbus_method_invocation_get_typeg_cclosure_marshal_genericg_signal_accumulator_true_handledg_signal_newg_param_spec_booleang_object_interface_install_propertyg_type_class_peek_parentg_type_check_class_castg_type_class_adjust_private_offsetg_object_setg_dbus_proxy_get_typeg_type_check_instance_castg_dbus_proxy_get_cached_propertyg_dbus_gvariant_to_gvalueg_variant_unrefg_value_set_variantg_variant_type_checked_g_dbus_gvalue_to_gvariantg_variant_newg_dbus_proxy_callg_object_notifyg_type_add_instance_privateg_dbus_proxy_call_finishg_quark_to_stringg_error_free__stack_chk_failg_object_unrefg_object_notify_by_pspecg_mutex_initg_main_context_ref_thread_defaultg_malloc0g_value_initg_list_prependg_value_copyg_param_spec_uintg_param_spec_variantmeta_plugin_get_typemeta_window_get_typeg_object_getst_texture_cache_get_defaultst_texture_cache_load_cairo_surface_to_gicong_themed_icon_newmeta_window_get_workspaceg_slist_prependmeta_window_showing_on_its_workspacemeta_window_get_user_timeshell_app_compareg_signal_emitmeta_context_restore_rlimit_nofileg_spawn_close_pidg_child_watch_addst_texture_cache_rescan_icon_themeg_strdup_printfg_data_output_stream_put_stringg_markup_escape_textstrcmpg_strdupg_hash_table_insertg_ascii_strtodg_markup_error_quarkg_set_errorg_ascii_strtoullg_file_readg_markup_parse_context_newg_markup_parse_context_parseg_input_stream_readg_markup_parse_context_freeg_input_stream_closeg_get_real_timeg_hash_table_iter_initg_hash_table_iter_nextg_hash_table_iter_removeclutter_effect_get_typecogl_object_unrefcogl_pipeline_copyclutter_get_default_backendclutter_backend_get_cogl_contextcogl_pipeline_newcogl_pipeline_set_layer_null_texturecogl_pipeline_set_layer_filterscogl_pipeline_set_layer_wrap_modecogl_texture_2d_new_with_sizecogl_pipeline_set_layer_texturecogl_offscreen_new_with_texturecogl_framebuffer_get_typegraphene_matrix_init_translategraphene_matrix_scalecogl_framebuffer_set_projection_matrixcogl_pipeline_get_uniform_locationcogl_snippet_newcogl_pipeline_add_snippetgtk_widget_get_typegtk_container_get_typegtk_window_get_typeg_getenvg_build_filenameg_file_testg_get_user_data_dirg_mkdir_with_parentsg_file_new_for_pathXDisplayNameg_get_user_runtime_dirg_settings_newg_strsplitg_strconcatgjs_context_get_typeg_object_newg_strfreevg_file_equalg_file_hashg_hash_table_new_fullg_cancellable_newg_bus_watch_nameg_dbus_connection_get_typeg_dbus_connection_call_finishg_dbus_proxy_set_cached_propertyg_io_error_quarkg_error_matchesg_clear_errormeta_stage_is_focusedclutter_stage_set_key_focusshell_action_mode_get_typeg_once_init_enterg_flags_register_staticg_once_init_leaveshell_app_state_get_typeg_enum_register_staticshell_app_launch_gpu_get_typeshell_blur_mode_get_typeclutter_actor_meta_get_typeg_param_spec_intg_param_spec_floatg_param_spec_enumg_object_class_install_propertiesshell_snippet_hook_get_typeshell_network_agent_response_get_typeg_static_resource_initg_static_resource_finishell_org_gtk_application_interface_infog_dbus_proxy_set_interface_infoshell_org_gtk_application_override_propertiesg_object_class_override_propertyg_dbus_interface_skeleton_get_typeshell_org_gtk_application_get_typeg_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_emitvg_type_add_interface_staticshell_org_gtk_application_get_busyg_type_interface_peekshell_org_gtk_application_set_busyshell_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_datalist_clearg_dbus_interface_info_lookup_propertyg_quark_try_stringg_datalist_id_set_data_fullg_variant_iter_nextg_variant_iter_freeg_variant_get_booleanshell_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_finishshell_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_dbus_method_invocation_get_method_infog_unix_fd_list_get_typeg_dbus_method_invocation_get_messageg_dbus_message_get_unix_fd_listg_dbus_error_quarkg_dbus_method_invocation_return_errorg_object_class_find_propertyg_object_get_propertyg_object_set_propertyg_variant_builder_initg_dbus_interface_skeleton_get_object_pathg_dbus_interface_skeleton_get_connectiong_variant_take_refg_variant_builder_addg_variant_builder_endg_list_free_fullg_source_destroyg_main_context_unrefg_mutex_clearg_mutex_lockg_mutex_unlockg_object_freeze_notifyg_object_thaw_notifyg_idle_source_newg_source_set_priorityg_object_refg_source_set_callbackg_source_set_nameg_source_attachg_source_unrefg_variant_ref_sinkg_dbus_interface_skeleton_get_connectionsg_dbus_connection_emit_signalg_variant_builder_clearshell_org_gtk_application_skeleton_newshell_net_hadess_switcheroo_control_interface_infoshell_net_hadess_switcheroo_control_override_propertiesshell_net_hadess_switcheroo_control_get_typeshell_net_hadess_switcheroo_control_get_has_dual_gpushell_net_hadess_switcheroo_control_set_has_dual_gpushell_net_hadess_switcheroo_control_get_num_gpusshell_net_hadess_switcheroo_control_set_num_gpusshell_net_hadess_switcheroo_control_get_gpusshell_net_hadess_switcheroo_control_dup_gpusshell_net_hadess_switcheroo_control_set_gpusshell_net_hadess_switcheroo_control_proxy_get_typeg_variant_get_uint32shell_net_hadess_switcheroo_control_proxy_newshell_net_hadess_switcheroo_control_proxy_new_finishshell_net_hadess_switcheroo_control_proxy_new_syncshell_net_hadess_switcheroo_control_proxy_new_for_busshell_net_hadess_switcheroo_control_proxy_new_for_bus_finishg_dbus_proxy_get_cached_property_namesg_strv_containsg_dbus_proxy_get_interface_nameg_dbus_proxy_get_object_pathg_dbus_proxy_get_nameg_dbus_proxy_get_connectiong_dbus_connection_callshell_net_hadess_switcheroo_control_proxy_new_for_bus_syncshell_net_hadess_switcheroo_control_skeleton_get_typeshell_net_hadess_switcheroo_control_skeleton_newgnome_shell_plugin_get_typeshell_app_get_typeg_param_spec_stringg_icon_get_typeg_param_spec_objectg_action_group_get_typeg_desktop_app_info_get_typeg_type_check_instance_is_ag_signal_connect_datashell_app_get_idg_app_info_get_typeg_app_info_get_idg_hash_table_lookupg_timeout_add_secondsg_source_set_name_by_idshell_app_get_iconmeta_window_get_client_typeg_app_info_get_icong_return_if_fail_warningshell_app_get_nameg_app_info_get_namemeta_window_get_wm_classg_dpgettextshell_app_get_descriptiong_app_info_get_descriptionshell_app_is_window_backedshell_app_create_icon_texturest_icon_newst_icon_get_typest_icon_set_icon_sizest_icon_set_fallback_icon_nameg_object_bind_propertyst_widget_get_typest_widget_add_style_class_nameshell_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_keyg_desktop_app_info_list_actionsmeta_window_get_gtk_application_object_pathmeta_window_get_gtk_application_idg_desktop_app_info_get_booleanshell_app_get_stateshell_app_get_n_windowsg_slist_lengthshell_app_is_on_workspacemeta_workspace_index_shell_app_new_shell_app_set_app_infog_utf8_collate_keyg_value_get_objectshell_app_get_busyg_value_set_enumg_value_set_booleang_value_set_stringshell_app_get_app_infoshell_app_update_app_actionsshell_app_compare_by_nameshell_app_system_get_typeg_hash_table_destroyg_source_removeshell_app_system_get_default_shell_app_system_notify_app_state_changedg_warn_messageg_hash_table_removemeta_window_is_skip_taskbarshell_app_system_get_runningg_slist_sortshell_app_system_searchg_desktop_app_info_searchg_utf8_validateshell_app_usage_get_typeg_str_equalshell_app_usage_compareshell_app_usage_get_defaultshell_blur_effect_get_typeclutter_paint_context_get_stage_viewclutter_actor_get_transformed_positionclutter_actor_get_transformed_sizeclutter_stage_view_get_scaleclutter_stage_view_get_layoutclutter_actor_box_set_originclutter_actor_box_set_sizeclutter_actor_box_scaleclutter_actor_get_paint_opacityclutter_actor_get_allocation_boxclutter_actor_box_clamp_to_pixelclutter_actor_box_get_sizeclutter_actor_node_newclutter_paint_node_add_childclutter_paint_node_unrefclutter_actor_get_sizecogl_pipeline_set_color4ubclutter_pipeline_node_newclutter_paint_node_set_static_nameclutter_paint_node_add_rectanglecogl_pipeline_set_uniform_1fclutter_layer_node_new_to_framebufferclutter_blur_node_newcogl_texture_get_widthcogl_texture_get_heightclutter_actor_box_get_originclutter_paint_context_get_framebufferclutter_blit_node_newclutter_blit_node_get_typeclutter_blit_node_add_blit_rectanglegraphene_matrix_init_scaleclutter_transform_node_newclutter_actor_meta_get_actorg_value_set_floatg_value_set_intshell_blur_effect_newshell_blur_effect_get_sigmashell_blur_effect_set_sigmaclutter_effect_queue_repaintshell_blur_effect_get_brightnessshell_blur_effect_set_brightnessshell_blur_effect_get_modeshell_blur_effect_set_modeg_value_get_floatg_value_get_enumshell_embedded_window_get_typeg_type_class_peekclutter_actor_get_typeclutter_actor_queue_relayoutclutter_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_newshell_global_get_typeg_cancellable_cancelg_hash_table_unref_shell_global_initg_object_new_valistshell_global_get_shell_global_destroy_gjs_contextshell_global_set_stage_input_regionmeta_is_wayland_compositorg_malloc_nXFixesCreateRegionmeta_display_get_x11_displaymeta_x11_display_set_stage_input_regionXFixesDestroyRegioncogl_context_get_displaycogl_display_get_renderermeta_plugin_get_displaycogl_renderer_get_winsys_idgjs_context_eval_module_filemeta_x11_display_get_xdisplaycogl_get_proc_addressXDefaultScreenstrstrexitshell_app_get_windowsmeta_window_is_override_redirectg_slist_reversemeta_display_get_workspace_managermeta_workspace_manager_get_active_workspaceg_slist_sort_with_datashell_app_activate_windowg_slist_findmeta_display_get_last_user_timemeta_display_xserver_time_is_beforeg_slist_copyg_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_pidsmeta_window_get_pid_shell_app_add_windowg_signal_connect_objectg_slist_nth_datameta_window_is_on_all_workspacesmeta_window_change_workspace_by_indexg_bus_get_syncgtk_action_muxer_new_shell_app_new_for_windowmeta_window_get_stable_sequence_shell_app_remove_windowg_slist_removeg_signal_handlers_disconnect_matchedg_signal_handler_disconnect_shell_app_handle_startup_sequencemeta_startup_sequence_get_completedmeta_startup_sequence_get_workspacemeta_startup_sequence_get_timestampmeta_display_unset_input_focusshell_app_request_quitmeta_window_can_closemeta_window_deleteg_action_group_get_action_parameter_typeg_action_group_activate_actionclutter_stage_get_key_focusmeta_display_get_current_time_roundtripmeta_focus_stage_windowmeta_display_focus_default_windowshell_app_launch_actiong_desktop_app_info_launch_actionshell_app_launchsd_journal_stream_fdg_desktop_app_info_launch_uris_as_manager_with_fdsg_variant_get_child_valueg_variant_is_of_typeg_variant_lookup_valueg_variant_get_strvg_app_launch_context_setenvshell_app_activate_fullg_dgettextshell_app_activateshell_app_open_new_windowg_app_info_should_showg_desktop_app_info_get_filenameg_app_info_get_executableg_app_info_get_commandlineg_app_info_get_display_nameg_icon_equalg_ptr_array_addshell_app_system_lookup_appshell_app_system_lookup_heuristic_basenameshell_app_system_lookup_desktop_wmclassg_ascii_strdowng_strdelimitshell_app_system_lookup_startup_wmclassg_file_replaceg_output_stream_get_typeg_buffered_output_stream_newg_data_output_stream_newg_ascii_dtostrg_output_stream_close_asyncshell_app_usage_get_most_usedg_ptr_array_newg_timeout_addg_hash_table_remove_allg_desktop_app_info_get_startup_wm_classg_hash_table_foreach_removeg_hash_table_foreachg_ptr_array_foreachg_ptr_array_freeg_str_hashshell_app_system_get_installedg_dbus_proxy_new_syncg_settings_get_booleanmeta_backend_get_typemeta_context_get_typemeta_display_get_typemeta_workspace_manager_get_typeg_settings_get_typest_focus_manager_get_typemeta_get_top_window_group_for_displaymeta_get_window_group_for_displaymeta_display_get_sizemeta_display_set_cursorfcntl64g_ascii_tableg_variant_lookupgnome_start_systemd_scopeg_file_get_typeg_file_delete_finishg_bytes_get_datag_file_replace_contentsg_task_return_errorg_task_return_booleang_cancellable_get_typeg_task_newg_task_set_source_tagg_task_get_nameg_bytes_refg_bytes_unrefg_task_set_task_datag_task_run_in_threadg_task_set_nameg_task_get_typeg_task_propagate_booleang_file_get_childg_variant_get_datag_variant_refg_variant_get_sizeg_bytes_new_with_free_funcg_file_delete_asyncg_file_get_pathg_mapped_file_newg_mapped_file_get_bytesg_variant_new_from_bytesg_mapped_file_unrefg_file_error_quarkclutter_offscreen_effect_get_typecogl_snippet_set_replacecogl_pipeline_add_layer_snippetclutter_clone_get_typeclutter_clone_set_sourceclutter_text_get_typeg_cclosure_marshal_VOID__VOIDgcr_prompt_get_typeg_type_check_value_holdsg_mallocg_task_get_source_objectg_async_result_is_taggedg_task_propagate_pointerg_task_propagate_intg_mount_operation_get_typestrchrg_queue_push_tailmemcpyg_string_newg_string_appendg_string_freeg_string_insert_cg_output_stream_write_allpolkit_agent_listener_get_typepolkit_unix_user_get_typepolkit_unix_user_get_uidgetpwuid_rg_idle_addg_list_removeg_cancellable_disconnectdcgettextpolkit_error_quarkg_task_return_new_errorg_list_foreachg_list_freeg_list_lengthmeta_rectangle_get_typegdk_pixbuf_save_to_stream_finishg_task_return_pointershell_screenshot_composite_to_streamcogl_sub_texture_newcairo_image_surface_createcairo_image_surface_get_datacairo_image_surface_get_stridecogl_texture_get_datacairo_surface_mark_dirtycairo_surface_set_device_scalecairo_createcairo_set_source_surfacecairo_paintcairo_destroycairo_surface_destroycairo_image_surface_get_heightcairo_image_surface_get_widthgdk_pixbuf_get_from_surfaceg_date_time_new_now_localg_date_time_formatgdk_pixbuf_save_to_stream_asyncg_date_time_unrefclutter_text_buffer_get_typeclutter_actor_get_preferred_heightst_bin_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_widthst_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_childclutter_actor_get_previous_siblingclutter_actor_is_visiblest_widget_navigate_focusclutter_actor_grab_key_focusg_object_class_install_propertyclutter_color_get_typeg_param_spec_boxedgtk_widget_destroyshell_global_get_stageshell_global_get_displayg_task_get_task_datameta_enable_unredirect_for_display_shell_global_get_gjs_contextshell_global_notify_errorg_signal_emit_by_nameshell_global_get_pointermeta_cursor_tracker_get_for_displaymeta_cursor_tracker_get_pointershell_global_get_switcheroo_controlshell_global_get_settingsshell_global_get_current_timemeta_display_get_current_timeclutter_get_current_event_timeshell_global_reexec_selfg_file_get_contentsmeta_display_closeexecvp__errno_locationg_strerrorshell_global_create_app_launch_contextmeta_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_workg_idle_add_fullshell_global_run_at_leisureg_slist_appendshell_global_set_runtime_stateshell_global_get_runtime_stateshell_global_set_persistent_stateshell_global_get_persistent_state_shell_global_locate_pointershell_glsl_effect_get_typecogl_pipeline_set_blendcogl_object_refshell_glsl_effect_add_glsl_snippetshell_glsl_effect_get_uniform_locationshell_glsl_effect_set_uniform_floatcogl_pipeline_set_uniform_floatshell_glsl_effect_set_uniform_matrixcogl_pipeline_set_uniform_matrixshell_gtk_embed_get_typegtk_widget_get_preferred_sizeshell_invert_lightness_effect_get_typeshell_invert_lightness_effect_newshell_keyring_prompt_get_typeclutter_text_get_textshell_keyring_prompt_newshell_keyring_prompt_get_password_actorshell_keyring_prompt_get_confirm_actorshell_keyring_prompt_completeg_task_return_intgcr_prompt_set_warningshell_keyring_prompt_cancelgcr_prompt_closeshell_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_messageshell_perf_log_get_typeshell_perf_log_get_defaultshell_perf_log_set_enabledshell_perf_log_define_eventg_hash_table_newg_queue_newg_get_monotonic_timeshell_perf_log_eventcogl_flushshell_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_errorshell_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_get_typegdk_pixbuf_save_to_streamshell_screenshot_screenshot_areag_task_report_new_errormeta_disable_unredirect_for_displayclutter_actor_queue_redrawshell_screenshot_screenshotshell_screenshot_screenshot_stage_to_contentshell_screenshot_pick_colorshell_screenshot_screenshot_finishshell_screenshot_screenshot_stage_to_content_finishshell_screenshot_screenshot_area_finishshell_screenshot_screenshot_window_finishshell_screenshot_screenshot_windowshell_screenshot_pick_color_finishcairo_image_surface_get_formatshell_screenshot_composite_to_stream_finishshell_screenshot_newshell_secure_text_buffer_get_typeg_utf8_offset_to_pointermemmoveclutter_text_buffer_emit_inserted_textgcr_secure_memory_reallocg_utf8_find_prev_charg_utf8_strlenclutter_text_buffer_emit_deleted_textgcr_secure_memory_strfreeshell_secure_text_buffer_newshell_keyring_prompt_set_password_actorclutter_text_set_buffershell_keyring_prompt_set_confirm_actorg_value_dup_stringshell_square_bin_get_typeshell_stack_get_typeshell_tray_icon_get_typeg_value_set_uintshell_tray_icon_clickclutter_event_typegtk_socket_get_typegtk_socket_get_plug_windowgdk_window_get_displaygdk_x11_display_get_xdisplaygdk_x11_lookup_xdisplaygdk_x11_display_error_trap_pushgdk_x11_window_get_xidgdk_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_buttonshell_tray_manager_get_typeg_value_set_boxedshell_tray_manager_newshell_global_get_window_actorsmeta_get_window_actorsmeta_window_actor_is_destroyedg_list_reverseshell_global_get_session_modeshell_global_get_debug_flagsshell_global_set_debug_flags_shell_global_set_pluginmeta_get_backendmeta_display_get_contextclutter_stage_get_typemeta_get_stage_for_displayst_entry_set_cursor_funcmeta_display_get_selectionst_clipboard_set_selectionclutter_threads_add_repaint_func_fullmeta_backend_get_settingsst_focus_manager_get_for_stageshell_gtk_embed_newshell_tray_icon_newg_object_ref_sinkmeta_window_get_xwindowmeta_window_get_compositor_privateclutter_actor_set_opacitycairo_region_creategdk_window_input_shape_combine_regioncairo_region_destroygdk_window_lowermeta_display_get_focus_windowclutter_actor_get_positionmeta_window_get_frame_rectmeta_window_actor_get_typemeta_window_actor_get_imagemeta_window_frame_rect_to_client_rectmeta_cursor_tracker_get_spritecairo_region_create_rectanglecairo_region_contains_pointmeta_cursor_tracker_get_hotcairo_image_surface_create_for_datacairo_surface_get_device_scalemeta_display_get_monitor_index_for_rectmeta_display_get_monitor_scaleclutter_actor_get_resource_scaleshell_tray_manager_unmanage_screeng_object_remove_weak_pointergtk_bin_get_typegtk_bin_get_childgtk_widget_get_displaygdk_x11_get_xatom_by_name_for_displayXGetWindowPropertygdk_x11_display_error_trap_popXFreest_theme_node_get_icon_colorsshell_tray_manager_manage_screeng_object_add_weak_pointercairo_pattern_create_rgbgdk_window_set_background_patterncairo_pattern_destroyg_signal_stop_emission_by_nameg_file_get_parentg_file_make_directory_with_parentsg_file_createg_output_stream_closeshell_util_touch_file_asyncg_dbus_connection_signal_unsubscribeg_task_get_completedg_task_return_error_if_cancelledsd_pid_get_user_unitg_dbus_connection_signal_subscribeg_io_error_from_errnoclutter_actor_iter_initclutter_actor_allocate_available_sizeclutter_actor_iter_nextclutter_layout_manager_get_typeclutter_actor_box_get_typemeta_rectangle_unionclutter_actor_box_equalclutter_layout_manager_layout_changedclutter_actor_destroymeta_size_change_get_typemeta_key_binding_get_typemeta_close_dialog_get_typemeta_inhibit_shortcuts_dialog_get_typenm_secret_agent_old_get_typenm_connection_get_typenm_secret_agent_error_quarknm_secret_agent_old_delete_secretsnm_setting_connection_get_typenm_connection_get_settingnm_setting_connection_get_uuidsecret_password_clearg_variant_dict_unrefnm_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_vpn_plugin_info_new_search_filenm_setting_get_namenm_setting_connection_get_idsecret_attributes_buildsecret_password_storevnm_setting_vpn_get_typenm_setting_vpn_get_service_typenm_connection_get_idnm_setting_vpn_foreach_secretnm_connection_for_each_setting_valuesecret_password_clear_finishg_ptr_array_unrefg_dir_openg_dir_read_nameg_hash_table_containsg_key_file_newg_key_file_load_from_fileg_key_file_unrefg_dir_closeg_key_file_get_locale_stringg_get_system_data_dirsgtk_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_displayXChangePropertyshell_util_set_hidden_from_pickg_object_set_datashell_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_composite_capture_imagescairo_savecairo_translatecairo_restoreshell_util_get_uidgetuidshell_util_start_systemd_unitshell_util_start_systemd_unit_finishshell_util_stop_systemd_unitshell_util_stop_systemd_unit_finishshell_util_systemd_unit_existsshell_util_systemd_unit_exists_finishshell_util_sd_notifyshell_util_has_x11_display_extensionXQueryExtensionshell_window_preview_get_typeshell_window_preview_layout_get_typeclutter_actor_box_get_widthclutter_actor_box_get_heightmeta_window_get_buffer_rectclutter_actor_get_preferred_sizeclutter_actor_get_fixed_positionclutter_actor_allocate_preferred_sizeclutter_actor_remove_childshell_window_preview_layout_add_windowclutter_clone_newclutter_actor_add_childshell_window_preview_layout_remove_windowshell_window_preview_layout_get_windowsshell_window_tracker_get_typeshell_window_tracker_get_window_appshell_window_tracker_get_defaultshell_wm_get_type_shell_wm_switch_workspaceshell_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_change_shell_wm_kill_switch_workspace_shell_wm_kill_window_effects_shell_wm_show_tile_preview_shell_wm_hide_tile_preview_shell_wm_show_window_menu_for_rect_shell_wm_show_window_menu_shell_wm_minimize_shell_wm_unminimize_shell_wm_size_changed_shell_wm_size_change_shell_wm_map_shell_wm_destroy_shell_wm_filter_keybinding_shell_wm_confirm_display_change_shell_wm_create_close_dialog_shell_wm_create_inhibit_shortcuts_dialogshell_wm_newshell_network_agent_get_typeg_hash_table_replacenm_setting_connection_get_connection_typenm_connection_get_setting_by_namenm_setting_enumerate_valuesnm_setting_wireless_get_typenm_setting_wireless_security_get_typenm_setting_802_1x_get_typenm_connection_get_uuidsecret_service_searchnm_setting_wired_get_typenm_setting_pppoe_get_typeshell_network_agent_search_vpn_pluginshell_network_agent_add_vpn_secretshell_network_agent_set_passwordg_variant_dict_insertshell_network_agent_respondg_variant_dict_endg_variant_dict_insert_valuenm_simple_connection_new_clonenm_secret_agent_old_save_secretsshell_network_agent_search_vpn_plugin_finishshell_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_getshell_app_cache_get_defaultshell_app_cache_get_allshell_app_cache_get_infoshell_app_cache_translate_foldershell_util_get_translated_folder_namena_tray_child_get_typegtk_widget_get_visualcairo_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_visualgtk_widget_set_visualgdk_visual_get_red_pixel_detailsgdk_visual_get_green_pixel_detailsgdk_visual_get_blue_pixel_detailsgdk_visual_get_depthg_list_remove_linkg_list_free_1gtk_widget_get_toplevelgtk_socket_add_idgtk_widget_showna_tray_child_get_titleg_strndupna_tray_child_has_alphacairo_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_rectna_tray_child_get_wm_classXGetClassHintg_string_append_unicharna_tray_manager_get_typena_tray_manager_newna_tray_manager_manage_screengdk_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_orientationna_tray_manager_set_colorsclutter_color_equalna_tray_manager_get_orientationopendirreaddir64strtoldirfdclosedirgetrlimit64sysconfshell_util_check_cloexec_fdsst_theme_context_get_for_stagemeta_settings_get_ui_scaling_factorgtk_container_addgtk_widget_show_allmeta_startup_sequence_get_typeshell_window_tracker_get_startup_sequencesmeta_startup_notification_get_sequencesmeta_window_get_transient_formeta_startup_sequence_get_application_idg_path_get_basenameshell_window_tracker_get_app_from_pidmeta_window_is_remotemeta_window_get_sandboxed_app_idg_str_has_prefixmeta_window_get_wm_class_instancemeta_window_get_startup_idmeta_startup_sequence_get_idmeta_window_get_groupmeta_group_list_windowsg_str_has_suffixg_direct_equalg_direct_hashmeta_display_list_all_windowsclutter_stage_get_capture_final_sizeclutter_stage_paint_to_contentcogl_framebuffer_clear4fcogl_framebuffer_draw_textured_rectangleclutter_texture_content_new_from_texturemeta_cursor_tracker_get_scaleclutter_stage_get_view_atclutter_stage_paint_to_bufferlibgnome-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-10.so.0libmutter-cogl-10.so.0libgraphene-1.0.so.0libwayland-server.so.0libX11.so.6libpolkit-agent-1.so.0libpolkit-gobject-1.so.0libgcr-base-3.so.1libsystemd.so.0libnm.so.0libsecret-1.so.0libmutter-10.so.0libXfixes.so.3libgnome-desktop-3.so.19libc.so.6libgnome-shell.soLIBSYSTEMD_209libnm_1_4_0libnm_1_0_0GLIBC_2.28GLIBC_2.4GLIBC_2.14GLIBC_2.3GLIBC_2.2.5/usr/lib/x86_64-linux-gnu/mutter-10:/usr/lib/gnome-shell	x ��b�xx0p�U�xp�U�xgx���	�xii
�x����xii
�xui	�x�d)��d)P��d)���d)@��d)e��d)s��d)n��d)b��d)r�e)j�(e)J�0e)��He)U�he)��pe)���e)���e)t��e)���e)�(f)��0f)��hf)��pf)t��f)��f)��f)��f)�(g)��0g)t�Pg)`e)`g) f)hg)�e)pg)�e)�g)�f)�g)�f)�g)`f)�g) g)�g)���g)���g)��h)��h)`g)h)Pg)(h)��Hh)��Ph)�g)hh)���h)���h)�g)�h)���h)�h)�h)@h)�h)h)�h)=��h)��h)��h)"� i)*�(i)6�0i):�8i)B�@i)8�`i)p7hi)P:pi)�;�i) e)�i)�d)�i)�d)�i)�j)�i)j)I� j)�(j)�0j)�j)�g)�j)���j)�h)�j)�j)�j)g��j){��j)��k)`�k)��k)�� k)��Hk)��Pk)��`k)��hk)��xk)���k),��k)��k)��k)��k)&��k)0��k)5�l)D�l)Z	 l)Z�(l)u�hl)��pl)���l)���l)���l)���l)���l)���l)��l)��l),��l)5�m)M�(m)U�0m)l�@m)q�Hm)��Xm)��`m)��pm)��xm)���m)X��m)���m)x��m)���m)���m)���m)���m)���m)	��m)!�n)'�n)=�@n)�0Pn)�&`n)3'pn)�&�)�) �)�`�)h7P�)X�)`�)%h�)|p�)�x�)���)���)���)���)���)1��)J��)���)���)�ȏ)�Џ)
؏)5�)P�)W(t)0t)8t)@t)Ht)Pt)Xt)`t)	ht)
pt)xt)�t)
�t)�t)�t)�t)�t)�t)�t)�t)�t)�t)�t)�t)�t)�t)�t)u)u)u) u)! u)"(u)#0u)$8u)&@u)'Hu)(Pu))Xu)*`u)+hu),pu)-xu).�u)/�u)0�u)1�u)2�u)3�u)4�u)5�u)6�u)7�u)8�u)9�u):�u);�u)<�u)=�u)>v)?v)@v)Av)B v)C(v)D0v)E8v)F@v)GHv)HPv)IXv)J`v)Khv)Lpv)Mxv)N�v)O�v)P�v)Q�v)R�v)S�v)T�v)U�v)V�v)W�v)X�v)Y�v)Z�v)[�v)\�v)]�v)^w)_w)`w)aw)b w)c(w)d0w)e8w)f@w)gHw)hPw)iXw)j`w)khw)lpw)mxw)n�w)o�w)p�w)q�w)r�w)s�w)t�w)u�w)v�w)w�w)x�w)y�w)z�w){�w)}�w)~�w)x)�x)�x)�x)� x)�(x)�0x)�8x)�@x)�Hx)�Px)�Xx)�`x)�hx)�px)�xx)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)��x)�y)�y)�y)�y)� y)�(y)�0y)�8y)�@y)�Hy)�Py)�Xy)�`y)�hy)�py)�xy)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)��y)�z)�z)�z)�z)� z)�(z)�0z)�8z)�@z)�Hz)�Pz)�Xz)�`z)�hz)�pz)�xz)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)��z)�{)�{)�{)�{)� {)�({)�0{)�8{)�@{)�H{)�P{)�X{)�`{)�h{)�p{)�x{)��{)��{)��{)��{)��{)��{)��{)��{)��{)��{)��{)��{)��{)��{)��{)�{)|)|)|)|) |)(|)0|)8|)	@|)
H|)P|)X|)
`|)h|)p|)x|)�|)�|)�|)�|)�|)�|)�|)�|)�|)�|)�|)�|)�|)�|)�|) �|)!})"})#})$})% })&(})'0})(8}))@})*H})+P}),X})-`}).h})/p})0x})1�})2�})3�})4�})5�})6�})7�})8�})9�}):�});�})<�})=�})>�})?�})@�})A~)B~)C~)D~)E ~)F(~)G0~)H8~)I@~)JH~)KP~)LX~)M`~)Nh~)Op~)Px~)Q�~)R�~)S�~)T�~)U�~)V�~)W�~)X�~)Y�~)Z�~)[�~)\�~)]�~)^�~)_�~)`�~)a)b)c)d)e )f()g0)h8)i@)jH)kP)lX)m`)nh)op)px)q�)r�)s�)t�)u�)v�)w�)x�)y�)z�){�)|�)}�)~�)�)��)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�Ȁ)�Ѐ)�؀)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�ȁ)�Ё)�؁)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�Ȃ)�Ђ)�؂)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�ȃ)�Ѓ)؃)�)�)�)��)�)�)�)�)	 �)
(�)0�)8�)
@�)H�)P�)X�)`�)h�)p�)x�)��)��)��)��)��)��)��)��)��)Ȅ)Є) ؄)!�)"�)#�)$��)%�)&�)'�)(�)) �)*(�)+0�),8�)-@�).H�)/P�)0X�)2`�)3h�)4p�)5x�)6��)7��)8��)9��):��);��)<��)=��)>��)?ȅ)@Ѕ)A؅)B�)C�)D�)E��)F�)G�)H�)I�)K �)L(�)M0�)N8�)O@�)PH�)QP�)RX�)S`�)Th�)Up�)Vx�)W��)X��)Y��)Z��)[��)\��)]��)^��)_��)`Ȇ)aІ)b؆)c�)d�)e�)f��)g�)h�)i�)j�)k �)l(�)m0�)n8�)o@�)pH�)qP�)rX�)s`�)th�)up�)vx�)w��)x��)y��)z��){��)|��)}��)~��)��)�ȇ)�Ї)�؇)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�Ȉ)�Ј)�؈)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�ȉ)�Љ)�؉)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)���)���)���)���)�Ȋ)�Њ)�؊)��)��)��)���)��)��)��)��)� �)�(�)�0�)�8�)�@�)�H�)�P�)�X�)�`�)�h�)�p�)�x�)���)���)���)���)���)��)��)��)��)ȋ)Ћ)؋)�)�)	�)
��)�)�)�)�) �)(�)0�)8�)@�)H�)P�)X�)`�)h�)p�)x�)��)��)��)��) ��)!��)"��)#��)$��)%Ȍ)&Ќ)'،)(�))�)*�)+��),�)-�).�)/�)0 �)1(�)20�)38�)4@�)6H�)7P�)8X�)9`�):h�);p�)<x�)=��)>��)?��)@��)A��)B��)C��)D��)E��)Fȍ)GЍ)H؍)I�)J�)K�)L��)M�)N�)O�)Q�)R �)S(�)T0�)U8�)V@�)XH�)YP�)ZX�)[`�)\h�)]p�)^x�)_��)`��)a��)b��)c��)d��)e��)f��)g��)hȎ)iЎ)j؎)k�)l�)m�)n��)o�)p�)q�)r�)s �)t(�)u0�)v8�)w@�)xH�)y��H��H��(H��t��H����5�(�%�(��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��������%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%�'D���%�'D���%�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%�'D���%�'D���%�'D���%�'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݿ'D���%տ'D���%Ϳ'D���%ſ'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݾ'D���%վ'D���%;'D���%ž'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݽ'D���%ս'D���%ͽ'D���%Ž'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݼ'D���%ռ'D���%ͼ'D���%ż'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݻ'D���%ջ'D���%ͻ'D���%Ż'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݺ'D���%պ'D���%ͺ'D���%ź'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݹ'D���%չ'D���%͹'D���%Ź'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݸ'D���%ո'D���%͸'D���%Ÿ'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݷ'D���%շ'D���%ͷ'D���%ŷ'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݶ'D���%ն'D���%Ͷ'D���%Ŷ'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݵ'D���%յ'D���%͵'D���%ŵ'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݴ'D���%մ'D���%ʹ'D���%Ŵ'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݳ'D���%ճ'D���%ͳ'D���%ų'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D���%�'D���%��'D���%��'D���%�'D���%�'D���%ݲ'D���%ղ'D���%Ͳ'D���%Ų'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%��'D���%}�'D���%u�'D���%m�'D���%e�'D���%]�'D���%U�'D���%M�'D���%E�'D���%=�'D���%5�'D���%-�'D���%%�'D���%�'D���%�'D���%
�'D��H�=�'�p���H�=�'�p�H�=!�'H��'H9�tH�V�'H��t	�����H�=�'H�5�'H)�H��H��?H��H�H�tH�-�'H��t��fD�����=Ų'u+UH�=��'H��tH�=�'����d������']������w������H�u6H�G(���H��'�@��H��FH�G(���H�%QH�GH��QH�G H��QH�G�f.���H�Պ'�@��H��eH�GH�JfH�G H��fH�G�f.���1��f���H�E�'�@���ff.��������H�G�f���1��f����ff.���UH��H����H��]�v��fD��UH��H�����H��]�V��fDH��H	�txATUSH��H��tSH��H��tK��H��A����A9�u6H�;H��t7E1�1��A�D$H�<�I��H��H��tH�t�-�����t�[1�]A\�f�[�]A\�fD��f.�H��H� t%L�-H�
�"��1�H�5����@H�G8H��tH�@H��tH�H���fDL���H�
r"��1�H�5������ff.�@AUATUL�'L;&t#L��H�
E&�m1�H�5���B���f�I�D$�H��I��H��DwH�(Hc�H�>��fD�s�I9���L������H�c�1�H��1��Z��1�]A\A]�f�H���X��L����N��@8�������H������L�������9������@H�����L������9������@H����L������9������y����H������L��H�����H9������W����H����L��H����H9������/����H�����L��fH~�����fH~�H9����������L���x��H��I���m��L��H����������������DL������H��I�����H��L��H	�t"H��tM��t]L��A\A]���1�������~���fDL���x���H��I���m���]L��A\H��A]���ff.�f�AUATUL�'L;&t#L��H�
-"�m1�H�5����f�I�D$�H��I��H��DwH��Hc�H�>��fD�3�I9���L�����H�#�1�H��1����1�]A\A]�f�H�����L������@8�������H�����L������9������@H���x��L����n��9������@H����L�����9������y����H�����L��H���}��H9������W����H���p�L��H���e�H9������/����H�����L��fH~����fH~�H9����������L���8��H��I���-��L��H���������������DL�����H��I���}��H��L��H	�t"H��tM��t]L��A\A]�k��1�������~���fDL���8���H��I���-���]L��A\H��A]�}���ff.�f�H��H�=y�����H��L�
��A�PjH�ƺ��PH�
ya����H���ff.�@H��H�=2����H��L�
��A�XjH�ƺ��PH�
i���H���ff.�@H��H�=��0��H��L�
�A��jH�ƺ��PH�
پ�D��H���ff.�@��AVAUATUH��S�l�H��H�uE1�jTH�ù�L�5��'P1�H�=x�L�-��'jjAVM�����H��0��H��H�uM��jTI��E1ɹ j@�H�=D�P1�SjjAV�n��H��8H�uM��jTE1ɹ�ATH�=�1�j@SjjAV�;��H�5�H��@1�H��H�=6�A������[H��]H��A\A]A^���fD��UH����H��'��'��u"H��P���H��wH�P0]��H�5٪'H�������ff.�@��AUI��ATI��H�=K�'U���P�~�L���L��Pj�A�jI��H��L�
p�1�H�
:�H�5?����XL��Z]A\A]����ATUH����t(L��H�
���1�H�5e����H��I����H��H���b��H�5�H���3��H��H��tH��L���p��H��H��]A\鑾���H��]A\����AU��ATUSH����v*L�cH�
\��1�H�5��i�f�H�9�'H��I��H����H��L�kH�����H��L�����H��C0u'H��t2L��H������H��H��[]A\A]���DH��L�����H��u�H��[]A\A]�D��AUATU��t"L��H�
D��1�H�5����I��H�=��H�����H��H������H�s�H�5��H��H��H�=��1��q��I���9�L��H������L��E1�1�H��H�,~'A�����PH�^H�57P��XH��Z]A\A]����f���AU��ATUSH����v*L��H�
��1�H�5i����f�H��'I��H��H��H�{����H��H������H�SH�5�H�=�H��H��1����I���d�L��H���	��SL��E1�H��H�81�A�����PH�5h���H��H��[]A\A]�1������H�5B���ATH�=v��"��H����L�
r�jH��A� �PH�
�m�6����(H��I���F���8�'XL��ZA\����USH��H��dH�%(H�D$1�H��H�$��H��t&H��膻��H�D$dH+%(uPH��[]��H�,$�}�|��H��L�E1�I���EH�KH���P1�����H�|$���XZ��1�����USH��H��dH�%(H�D$1�H��H�$��H��t&H�����H�D$dH+%(uPH��[]��H�,$�}����H��L�E1�I���EH�KH�9�P1��$��H�|$�
���XZ�������AT1���1�UH��H���H������H���HDž�H��t�Q���L�%Ҥ'H��P����H��L��]H��A\�[��ff.���USH��H��Hc=�'H�H�{ H�� �|��H�k ���H�E�E��H��H�C H�8H��[]��ff.���USH��H��Hc=|�'H�H�{ H�� ���H�k �#��HH�E����H��H�C H�8�a�H�C �H�8H���L�H�C �TH�8H��[]H��0�1��AVA��AUATI��UH��SH��H�G �fDH�H9tXH�@H��u�(�e�H�H�] H��I��D�pI��H�{�W��I�4$L��H�C���[L��]L��A\A]A^�D�@[]A\A]A^����UH�5�H��1�H��A��H�=��j���H��H������H��E1�1�h�H�5��A�����H�=��H������H��H�����H�=��螼��H�5��H�=��E1�H��H��A���\��H��H��XZ]鮽��ff.���UH�������'���=���H��H�����H���H�
��H���H�5ɎH��H���H�
D�H�=�H���H��H���H�
��H���H�5s�H���H�ՐH���H�
'�H���H�=��H���H�5�H���H�m�H���H�
�H���H�=ђH���H�53�H���H���H���H�
w�H��H�=ٓH��H�5;�H��H��H�� H��(]�DH�5a�'H��虺�����@UH�=2����H���K��H���0H��jH��H�
c���L�
l�A�8葹��ZY]�ff.�f���ATI��UH��H��dH�%(H�D$1�����L��H��蟾��H�}(H�E(I��H��t���H��1�H�5T�L��1��������H�4$H���o��H�E(H��t6H��PL�%O�'�B���H��H�D$dH+%(u!H��L��]A\���H�=D��$��H�E(��y��f���UH��SH��H��H�>t赽��H9tH���[]�@H�[H��H�;豻��H�H���[]�f���AWAVAUI��ATI��USH��H���`���L�sL��H��L9�A���J���H;C��tE��u|L9�t��t�H��[]A\A]A^A_��L���0��L����&����t2��t>��t*L��貴��L���訴��H��A����[]D)�A\A]A^A_Ð��u���f.�������f���AUATUSH���G;Ft"���#A�H��D��[]A\A]�DH�G8H��I��H��t*H�XH��u�H�[H����H�;�c����t�I�D$8H��t=H�XE1�H��u�@H�[H����H�;�+����t�E���m���fDE1�}�`���H�E8I�T$8H�xH�ZttH��u�bH�[H����H�;�{���A9�w�H�;�n���A����f�I�D$8H������H�XH��t�A��k���fDA���v���A���������H������H�XH������1��f�H�[H��t&H�;���9�w�H�;�������H�E8H��u����A)��~���f���H��H��H�R8H��t%H�JH9t�J�5"�'1�1��������H��L�1�1���H�
o
H�5���S���H��1�H�5�dH�%(H�D$1�H�����H�<$1��L�H�D$dH+%(uH�������f�����H�5��'1����ff.����SH�_����H������K$�Q��w��u�S$�[ÐH�C 1�[�@AV1�I��AUI��ATI��H�=o�US���1�L��L��H��H���^��H����4�����u[1�]A\A]A^�DH�����L�����1�L��L��H��H�����H�������t�[L��]L��1�A\H�5��A]A^��@��AWAVM��AUM��ATI��H�5��UL��H��SH��H�������tH�5��L���m����uH��[]A\A]A^A_�f.�H�5�5L���A������H�H���H��H����8i���xd���x��H�9���H�����H�D$���I�~PH�t$H��I���?���L�#M���Z���L�-�L�5
��+L��L���������L�cH��H��M������L��L���}����u�H�}1��N���A����˿��M��H�
����H��L��1�[]A\A]A^A_��f�H�BH��H��H�������膿��M��H�
,�����H�}�
1��`����I�G�H���DAV1�AUI��ATUSH��PH�dH�%(H��$H1�H��H�$H���2���H�<$H��tY�u3�>���H��$HdH+%(�5H��P[]A\A]A^��H�O1�H����1�����H�<$뮐1�L��1�H��H�=��'L�d$@�e��I���H��L��L���B����t�I��1�L��H���i��H��H���L��L�d$���1�1�H�����H��H�l$������I�uPH��H��4�ׂ�CH��H��H��?H��H)�H���������1�L��H���S����t'H�D$��
f/v�H;X~�H��������@H�$H�������H�H����}��ff.�f�UH�=���c��H���[���H���H��jH��H�
�L�
�A���q���ZY]�ff.�f���UH��H�8H�E8H��t���H�}(H�E(H��t���H�}XH�EXH��t�v��H�}HH�EHH��t���H�}pH�EpH��t�J��H�}`H�E`H��t蔬��H�}0H�E0H��t���H�}PH�EPH��t���H�}hH�EhH��t����H�=c�'�P�Q��H��]H�@0���H�=	�'H��t�O���H�����H�����H������1�H��H�Ӗ'���H�=ǖ'�&1��&����H�=��'�/�1���H�=��'H������AUATA��U��SH��H��x�D$dH�%(H�D$h1��3��H�����H�{H�CI��H��t���H�;H�H��t�N���f�����%b�I*��^L$(�(�T�.�v,�,�f���5=U��*�(����T��\�(�V�f���H*��^\$(�T�.�v,�,�f���-�
U��*�(����T��\�(�V��,�L���\$E1��,��L$���H�CH��H����H�{1�����H�{�<��H������H��H������L$�\$H��H�H�����%f
��
(�L�d$ W�W�L���L$�Y�H�t$�\$�Y��D$�T$�D$�۶���L$L���
�\$��	�^��
�	�^�跽��L��H��A����H�D$hdH+%(u4H��xD��[]A\A]��H�
���1�1�H��������]��ff.�f���USH��H���G|HLJ��?���H�C0��H�=�'H�CPH��t#�>��H�5K�H�ChH���K���CxH��[]Ð���H�l��H�5��H���'���H�=��'H��H���Z��H���R��H�=��'�f���ATUH��S�P��H�Y�'�K�'��uH��P���I���گ��H��H���o��H���ǹ��H��H���\��H���H�
nsI�T$H�tH���H�
T�H���H�ftH���H��H[]A\�H�5��'H�����m���ff.��ATH�=&�����I������H��L��0jH��L�
���A�8H�
�������H��I�������R�'XL��ZA\�f���AWAVAUATI��H�=��USH�����H�=��H�����H��H�5��I��H���HD�1�1�I��$�H�������H��I��������M��$����1�H�5d�H��1��ܲ����I��$�H���ǩ��I��$����1�I��$�����I���c��L��H�R�H�=��H��1�踸����H��H���x���H���`��H��I��$��P���H�=�褨��I�D$xM���~�����H�5�L���"���H�(I��H����H�D$�E1�E1��[�O�t�I�H��t`1�H�5��H��A���^��H��H�D$H�8�Υ��I�>�ƥ��Mc�I�l�Hc�H��K��H�)H�L$H��tH�5}�H���w����t�A����DI��H�����L��1�H�5o�H��1��¥��L��I�D$`�e���H�N�'H�5_�'H�=(�'H���h���I��$��ۼ��H��M��1�jL���H�
r1�I��$�H�5P��ټ��H��([]A\A]A^A_�f.�L���ؤ��H��H�=��1�����I���������.��H�=��I���/���I��������AUI��ATI��UH��H��dH�%(H�D$1�H�$�]���L��I��H�����L��L��H������H��tTH���H��H�5�����L�%ǎ'�PH���¬��H��L���W���H�D$dH+%(uVH��]A\A]�D�3���H�<$����c�����tL������DH�$H�%���1�H�H1��C��������ff.����SH�z0H���?�����t[�f�H�{1�[�$���@��H�Տ'H��tH�ɏ'��UH�-��'H��萻����uH���']�H�=(��4���H�5}i'H���%��H��H���z��H�s�']Ð��H�]�'H��tH�Q�'��UH�-@�'H��� �����uH�-�']�H�=���Ľ��H�5�h'H������H��H���
��H���']Ð��H��'H��tH�َ'��UH�-Ȏ'H��谺����uH���']�H�=f��T���H�5�g'H���e��H��H�����H���']Ð��H�m�'H��tH�a�'��UH�-P�'H���@�����uH�=�']�H�=����H�5
g'H�����H��H���*��H��']��ATUH��S�p���
��'H���'���#H��P���I�����H��H�����H���s���H��H���x��H�
A�H��H�ffI�L$0H�
kH�5p�E1�I�T$ H�TeA����H�=Z�I�L$1�H���H�rZH��H���h�@�^��H�50���@�H��f�H�= �H�͋'(����H�Ƌ'���H�5��H�=��E1�H��H��A��@�O���L��H���'XZH�w�'[]A\�>���fDH�5��'H���i�������@��H���'H��tH���'��UH�-x�'H���p�����uH�e�']�H�=g�����H�5}d'H���%��H��H���Z��H�3�']Ð��H��'H��tH��'��UH�-�'H��������uH��']�H�=�褺��H�5�c'H�����H��H������H���']Ð��H�Ec'�@��ATUH��H��Hc{�'H�H�G ����I���'��H��H���̧��H��L��]H��A\����f.���������ATH�%�A��蛶��D��A\�D��ATI��UH�����H���'��'��utL��P����H��H��
H�E0H����H�E H���H�E�v��L��H�����H��H��H�
�
H����H���H��]A\�K���H�5q�'L���I����x���@��UH������H�<�'�.�'����H��P�!���H��H��H�G0H�'H�G H��H�GH��H�G@��������H��H������H�����H�
:H���H��H���H�
���H���H���]�fDH�5��'H���y����V���@��H���'H��tH���'��ATUH�-v�'H��H���z�����uH�_�'H��]A\��H�=������H��E1�E1�j�0H�ƿH�
���/����PI��H������L��H���4��XZH���'H��]A\���AWH��AVI��H�={`'AUATUH��SH��dH�%(H��$�1��c���H���H��I�������H�D$H��H��H�D$�d��I������L��I�_H��H�D$�(��L��L��L�t$ �غ��H��L��1�軟���1f��TH������L��H��踢��L��H��H���؛��L��谯��I��H��t I�D$H�(�@ ��u�H��L���}�����I�|$ H�t$1�L���Ğ��1�1�L�����v���H�|$t fDH��H������H��H��H;D$u�L���F���H��$�dH+%(uH�ĸ[]A\A]A^A_��<���ff.��ATH�=��H�� dH�%(H�D$1�����I������H��L��L�
����jA�(��H��H�
������H��I���,���H�D$�݆'H���H�D$H�D$ �3���H�T$L��H���s���XZH�D$dH+%(u
H�� L��A\��r���f�ATH�=��H�� dH�%(H�D$1��N���I���6��H��L��L�
�jA�(�0H��H�
���\����(H��I���l���H�D$��'H�f��H�D$H�D$ �s���H�T$L��H��賟��XZH�D$dH+%(u
H�� L��A\�貶��f���UH���3���H�}H���ǣ��H��]H�@(��ff.�f���ATA��P���D��1�A\H��H�5~�1�饹��D��AV1�I��H�=��AUM��ATUH��SH���:���I�����L��H��觡��AUL��I��UH��A�����1�H�5�����XZ[]A\A]A^�ff.����AUI��ATI��UH��SH�����L��H���I���L��H��H�����H��H��tH��H�5W�1��r���H���z���1�H����H��[]A\A]����AU1�I��H�=
�ATUH��SH��H���[���I���#��L��H���Ƞ��H��I��1�UL��H��A�����H�5��膱��ZYH��H��tH��H�5��1��۴��H�����1�H����H��[]A\A]���AU1�I��H�=��ATUL��SL��H���˦��I�����L��H���8����t$0I��L��UH��A�����1�H�5Y����H��[]A\A]�ff.���AUI��ATI��UH��SH���4��L��H���ٟ��L��H��H���;��H��H��tH��H�5��1�����H���
���1�H����H��[]A\A]����AU1�I��H�=��ATUL��SL��H�����I�����L��H���X���H��I��1�UL��H��A�����H�5y�����ZYH��H��tH��H�5P�1��k���H���s���1�H����H��[]A\A]���AU1�I��H�=1�ATUL��SL��H���[���I���#��L��H���Ȟ���t$0I��L��UH��A�����1�H�5�����H��[]A\A]�ff.���AVI��AUI��ATI��UH��S����L��H���h���L��H��H������H��H��tH��L��H�5��1�莲��H��薕��1�H��[]��A\A]A^�D��AU1�M��ATUH��H�=K�SL��H���{���I���C��H��H�����H��I��1�t$8L��H��A�����H�5�裮��ZYH��H��tH��L��H�5	�1����H�����1�H����H��[]A\A]�f.���UH�=��1�H�����H��]H���N���ff.���UH�=r�1�H��躣��H��]H������ff.���UH�=q�H��1���舣��H��]H�����ff.����H�m�'H��tH�a�'��UH�-P�'H���p�����uH�=�']����H��H���p���H�!�']����UH�����H��H���x���H�x ���H�=�'�P����H��]H�@0��@��AWI��AVL�5~W'AUATI��UH��SH��(dH�%(H�D$1�L�l$�$���H��H���	���L��H�T$H�5�H��1�L�%O��9����9�H�t$L������H�|$H�����H�} 1�1҉�����H����H�|$1�1�L��L��身����u�H�|$�<���I�7H��tkM��E1�L�-�V'�L��訿��I�>H���}���H�} 1�1҉�讔��H��tL�s(�PH���8���H��L���-���A�D$M�4�I��I�6H��u�H�D$dH+%(u:H��([]A\A]A^A_�f.�H�[(H��P�ߚ��H��H���Ե���!����:���f.���ATE1�UH��H�����H��H��蟚��H������H��H��茚��H�5��H���]���H��tH��H���=���H��A���‘��H��D��]A\����AWI��AVI��AUM��ATM��UH��S��H���8���jL��L��H��H���L�
n�1�PH�l�PH�u�AWPH�y�AVPH��UP1�SL��$��g���H��X[]A\A]A^A_����AUI��ATI��U�,���H������H��H��虙��L��L��H�����H��I����M��t�v�]L��A\H��A]�f���fD]1�A\A]����AWI��AVI��AUI��ATM��UL��S��H���8���jA��L��H��H���L�
�H��PH�k�H�
\�PH�m�AWPH�q�AVP1�AU����H��@H��t&H�����H��H��[H��]A\A]A^A_鵘��DH��1�[]A\A]A^A_�ff.�@��AWI��AVA��AUM��ATM��UH��S��H���x���jL��L��H��H���L�
��1�PH���PH���AWPH���AVPH�UP1�SL��$�觲��H��X[]A\A]A^A_����AUI��ATI��U�l���H���T���H��H���ٗ��L��L��H���;���H��I���0���M��t��]L��A\H��A]馗��fD]1�A\A]����AWI��AVA��AUI��ATM��UL��S��H���x���jA��L��H��H���L�
(H��PH���H�
��PH���AWPH���AVP1�AU�O���H��@H��t&H����H��H��[H��]A\A]A^A_���DH��1�[]A\A]A^A_�ff.�@��H�5z'H��tH�)z'��UH�-z'H���P�����uH�z']���H��H���P���H��y']����AWAVAUATUL��SH���H��$ H�L$ L�D$(H�\$dH�%(H��$�1��Y���H��$(H������f�H��H�D$@I��)D$0�,���H���3I��H���س��A�|$0��H��H�H��H��H�$�B���I����L��H��H�D$�
���L��L��M�w軫��薬��L��H�����H�t$L��螫��A�D$0����<L�t$PH��H�T$L���g���H�T$H�RH)�I�,�H���-D�TH��苻��L��H���P���L��H��H���p���L���H���I��H��t I�D$H��P ��u�H��L��������I�|$(H�t$H�l$0�\����H��A������1�L��H��D�����H���T�������H��1�L���_���H�<$t�H��H��H���@���H;$u�L��貋��H��$�dH+%(��H���[]A\A]A^A_�f��;���M�w0H��L���|���H�|$貤��H���z���L��H����������D����L�L$ L�D$(H�
��H�|$�ƺ1�藯���&���f�L���H�
r��a1�H�5�������
���f.���AVAUM��ATUL��SH�� dH�%(H�D$1����H�|$PH���X���f�H��H�=�N'H�D$I��)$�u���H����H�p(I�>H��蝮��I��H��tuH��I�t$H���e���L�c(�PL����H��L��H���&���H�{譎��H��H��肞��H��I��藼��H�D$dH+%(u`H�� L��[]A\A]A^��車��I��L���H�
%�1�萼���fDL�A�H�
����1�H�5=��׹���¦��f���AVAUM��ATM��USH�� dH�%(H�D$1�L�t$P�P���H�|$XH������f�L��H�=EM'H�D$H��)$�0���H����H�p(H�}H���W���H��trI��C0uYL��L�����L�k(�PH��譑��L��H��L���?���L���g����H�T$dH+%(ukH�� []A\A]A^�DL��L��蕏����{���M��L����H�
��1��P���1��@L��H�
j���1�H�5��藸��肥��f���AVAUATUH��H��dH�%(H��$�1�����H��H��H���Ր��H�=��I��薌��H��H���+�������L��H��I��訐��H���P���L��L��I��蒐��H���z���H��E1�L��ATH��L��1�H�
�����H��H��t.I��H��胙��H��L��1�H���H�5R��H���L��萇��H���؍��H��$�dH+%(uH�Ę]A\A]A^��q������UH��SH������H��H�����H��H�@ H�8褹��H�C H�8����H�C H�5}���H�x�ԣ��H�C H�xH��t	肺��H�C H�x���H�{ H�� ����H�=s'�P���H��H�@0H��[]�����AUI��ATI��U��SH���u���L��H���:�����t%L�n�H�
'��91�H�5��蔶��@H��H�@ H�x ��H�C L��H�8���H�{ H��[]H�� A\A]�ڦ��f.���AVI��AUA��ATI��UH��S���H��H��討��A��t"L���H�
T���1�H�5g������H��H�@ H�x �`���H��踝��H�C L��H�0�i�����t%H�{ H�� �H���[H��]A\A]A^�Ȏ���苶��H��H��� ���H������H��tH�C �H�5cF'H��H��(��H�C L��H�0�٬��L��H���~����ff.����UH��SH�����H��H��賍��H��H�@ H�x 蓡��H�] H�{tH�{tH��H�{ []邥��f��˒��1�H�CH�E H�x�x���H��耄��H�
�m'H�5bH��H�E H�x��H�E H�5�H�x����H�E H�pH�x�
���H�E H�x�p���H�] H��H�{ []���ff.�f���AWAVAUATUH��SH��(dH�%(H��$1�L��$����H��H��譌��H��H�@ H�x 荠��H�=���a���L��H�����H�=6��J���H�|$H��H�|$�ؚ��H�M H�YH����E1��f�H�[H��tsH�M L�3A�F��H�4@H�L�<�I�vL���2�����u�I�A��H�x�އ��L��H��賗��H�5�L��I��I�L��H�P1����L���=���H�[H��u�E���H�L$L��H�5D�H�=o�1�L�5,��'���H���?���I�����H��H��H��艋��H��衯��H�D$I��H��tE@H��H��M�/�b���H���
���H��M��L��jH��L�	�1�L������M�XZM��u�L��臂��H�5pk'H�|$�6���H�E H�5˺��H�x�"���H�} H�GH�� H�G����H��$dH+%(u.H��(1�[]A\A]A^A_�fDL��谈��H�|$覈�������ff.�@��UH����H��H���x���H��H�@ H�x �X���H�} L�GM��t+L���3���H�} H�GH�� �>���H��]�E���DH�� ]�&���fD��ATI��SH���=�L��H������H��H�@ H�x ���H�C H�8薗��H�{ A��H�� �֡��H��D��[A\�ff.���U��H�����1�H��1��R���H��]H��閉��fD��H�D'�@��ATUH��H��Hc�l'H�H�G ����I��觶��H��H���L���H��L��]H��A\�Z���f.���������ATH��UH��S��D�c�����sH��H������D��H��H�����D��[]A\����ATI��UH�����H�l'�l'��utL��P����H��H��H�E0H�j���H�E H����H�E�Ƶ��L��H�����H��H��H�
�H����H���H��]A\����H�5�k'L��虃���x���@��UH���C���H�Tk'�Fk'����H��P�q����H��H��H�G0H��H�G H�H�GH�AH�G@�������H��H���(���H�q���H�
H���H�LH���H�
����H���H���]�fDH�5�j'H���ɂ���V���@��H��j'H��tH��j'��ATUH�-�j'H��H���ʖ����uH�wj'H��]A\��H�=���d���H��E1�E1�j�(H�ƿH�
��������PI��H���o���L��H��脧��XZH�j'H��]A\���AWH��AVI��H�=A'AUATUH��SH��dH�%(H��$�1�賝��H���H��I���?����H�D$H��H��H�D$质��I������L��I�_H��H�D$�x���L��L��L�t$ �(���H��L��1������1f��TH���C���L��H������L��H��H���(}��L������I��H��t I�D$H�(�@ ��u�H��L���͇����I�|$ H�t$1�L������1�1�L�����ƅ��H�|$t fDH��H���,���H��H��H;D$u�L���|��H��$�dH+%(uH�ĸ[]A\A]A^A_�茙��ff.��ATH�=��H�� dH�%(H�D$1��^���I���6���H��L��L�
X���jA�(��H��H�
��l���H��I���|���H�D$��g'H�����H�D$H�D$ �3���H�T$L��H���Á��XZH�D$dH+%(u
H�� L��A\��˜��f�ATH�=/�H�� dH�%(H�D$1�螖��I��膬��H��L��L�
���jA�(�0H��H�
���~���(H��I��輚��H�D$�g'H����H�D$H�D$ �s���H�T$L��H������XZH�D$dH+%(u
H�� L��A\�����f���UH���3���H�}H������H��]H�@��ff.�f���ATA��P�M���D��1�A\H��H�51�1����D��UH������H�}H��跄��H��]H�@ ��ff.�f���ATA��P���D��1�A\H��H�5�1�镚��D��UH���s���H�}H���W���H��]H�@��ff.�f���H���PdH�%(H�D$1��~���H��1�H�5��H��1��ؚ��H�$H�T$dH+%(uH���躖��f.���ATI��P�-���L��1�A\H��H�5*�1��ՙ��D��H�Ue'H��tH�Ie'��UH�-8e'H��萑����uH�%e']����H��H��萢��H�	e']����UH�����H��H��蘁��H�x ����H�=�d'�P���H��]H�@0��@��AWI��AVL�5�;'AUATI��UH��SH��(dH�%(H�D$1�L�l$�$���H��H���)���L��H�T$H�5.�H��1�L�%o��Y����9�H�t$L���3���H�|$H������H�} 1�1҉��7z��H����H�|$1�1�L��L���ڐ����u�H�|$�\y��I�7H��tkM��E1�L�-;'�L���Ȥ��I�>H��蝛��H�} 1�1҉���y��H��tL�s(�PH���X���H��L���M���A�D$M�4�I��I�6H��u�H�D$dH+%(u:H��([]A\A]A^A_�f.�H�[(H��P���H��H�����!����Z���f.���ATE1�UH��H�����H��H�����H������H��H�����H�5��H���}���H��tH��H���]���H��A����v��H��D��]A\����ATE1�UH��H���J���H��H���O��H��藬��H��H���<��H�57�H���
���H��tH��H��荘��H��A���rv��H��D��]A\����ATI�����L��H����~��I���/���L��H����~��H�5ҰH��襃��I��H��tH���v��L��A\�ff.�@��AWI��AVI��AUM��ATM��UH��S��H���h���jL��L��H��H��L�
��1�PH���PH�ŲAWPH�ɲAVPH��UP1�SL��$�跘��H��X[]A\A]A^A_����AUI��ATI��U�|���H���d��H��H����}��L��L��H���K���H��I���@u��M��t�v���]L��A\H��A]�}��fD]1�A\A]����AWI��AVI��AUI��ATM��UL��S��H���h���jA��L��H��H��L�
8�H��PH���H�
��PH���AWPH���AVP1�AU�_���H��@H��t&H������H��H��[H��]A\A]A^A_�}��DH��1�[]A\A]A^A_�ff.�@��AWI��AVA��AUM��ATM��UH��S��H�����jL��L��H��H�H�L�
��1�PH���PH��AWPH��AVPH�R�UP1�SL��$����H��X[]A\A]A^A_����S1�H����1�H���#~��H��1�L���SH���L�
��H�
���&���XZ[�f���AUI��ATI��U�l���H���T}��H��H����{��L��L��H���;���H��I���0s��M��t�f�]L��A\H��A]�{��fD]1�A\A]����AWH��AVAUATUH��SH��dH�%(H�D$1�I��H�$L���U���H����I��蔨��L��H���9{��H�r���1�H���1��
}��H�������I��H����L�-�H��L���������SH���L���舕��L��H�=߯H��1��D���H���H��襅��H���I���f���H���I���׊��UI��L���H��H�B��L��L��PH�
�AWj�jj�ל��H��0L��軍��H�D$dH+%(�&H��[]A\A]A^A_��H���L����͔��H��H�= �H��1�腀��H���H�����H���I��觀��H���I������UI��L��H��H����L��L��PH�
5�AVj�jj����H��0�D����L�-�['H��P�y��H��L������L���ь������@�t��H�<$����3�����tL���ז�����f�H�$H�E���1�H�H1��{����茍��ff.����AWI��AVA��AUI��ATM��UL��S��H������jA��L��H��H�x�L�
��H��PH�+�H�
�PH�-�AWPH�>�AVP1�AU�ϊ��H��@H��t&H���>�H��H��[H��]A\A]A^A_�ux��DH��1�[]A\A]A^A_�ff.�@��H�}['H��tH�q['��UH�-`['H���Ї����uH�M[']���H��H���И��H�1[']����AWAVAUATUL��SH���H��$ H�L$ L�D$(H�\$dH�%(H��$�1��Y���H��$(H���w��f�H��H�D$@I��)D$0謌��H���3I��H���X���A�|$0��H��H�H��H��H�$�™��I�����L��H��H�D$芝��L��L��M�w�;�������L��H���k���H�t$L������A�D$0����<L�t$PH��H�T$L����q��H�T$H�RH)�I�,�H���-D�TH������L��H����t��L��H��H����m��L���ȁ��I��H��t I�D$H��P ��u�H��L���x����I�|$(H�t$H�l$0��p���H��A��蜜��1�L��H��D���|v��H���ԃ������H��1�L���ߟ��H�<$t�H��H��H�����H;$u�L���2m��H��$�dH+%(��H���[]A\A]A^A_�f�軝��M�w0H��L�����H�|$�2���H�����L��H��蟋������D�m��L�L$ L�D$(H�
r�H�|$�ƺ1������&���f�L�	�H�
����1�H�5�蟜��芉��f.���AVAUM��ATUL��SH�� dH�%(H�D$1����H�|$PH����t��f�H��H�=J/'H�D$I��)$���H����H�p(I�>H������I��H��tuH��I�t$H�����L�c(�PL���tt��H��L��H��覇��H�{�-p��H��H������H��I������H�D$dH+%(u`H�� L��[]A\A]A^���;l��I��L���H�
��1������fDL���H�
J���1�H�5פ�W����B���f���AVAUM��ATM��USH�� dH�%(H�D$1�L�t$P�P���H�|$XH���s��f�L��H�=.'H�D$H��)$谗��H����H�p(H�}H���׎��H��trI��C0uYL��L���nu��L�k(�PH���-s��L��H��L��迎��L������H�T$dH+%(ukH�� []A\A]A^�DL��L���q����j��M��L����H�
e�1����1��@L���H�
����1�H�5����������f���AWL�=�,'AVAUATUH��SH�1('H��dH�%(H��$�1�L�l$���H��1�H���@r��H�=M�I���n��L��H��薀������EI��H��H�����C�t�L�CL�D$�P���L��H��H�$��q��H��艆��H�4$L��I����q��H���k��H��L��E1�ATL�D$H��1�H�
G����I��XZM���z���L���z��H�S1�L��H�5��L��肚��L����h���EI��H��H���R����L���n��H��$�dH+%(uH�Ĩ[]A\A]A^A_�莅��ff.���UH��SH�����H��H����p��H��H�@ H�8贚��H�C H�8H��褚��H�C H�8H��0蔚��H�C H�8�h��H�C H�5����H�x�Ą��H�C H�xH��t	�r���H�C H�x��l��H�{ H�� �j��H�=�S'�P���H��H�@0H��[]�����ATI��UH��S������H��H���-p����v(L���H�
���p1�H�5�臗���H��H�@ H�x ���H�E H�[L��H�H�<��Ɏ��H�} []A\H�� �ȇ�����AWAVI��AUA��ATI��UH��SA�]�H���I���H��H���o����v)L��H�
����1�H�5h�����I��H��)'H��H�[H��H�D$I�G H�x �(���H���~��I�G L��H�0H��n�����t*I� H�� �
���H��H��[]A\A]A^A_�o����K���L��H����n��H����h��H��tH�D$�@0u(I�G L��HH��覍��L��H���K����f�I�G H�t$D��L��H�H�趬���@��UH��SH������H��H���cn��H��H�@ H�x �C���H�] H�{tH�{tH��H�{ []�2���f��{s��1�H�CH�E H�x�(s��H���0e��H�
YN'H�5bH��H�E H�x袎��H�E H�5W�H�x辆��H�E H�pH�x�}��H�E H�x� s��H�] H��H�{ []魅��ff.�f���AWAVAUATUH��SH��(dH�%(H��$1�L��$�����H��H���]m��H��H�@ H�x �=���H�=Z��i��L��H���{��H�=��h��H�|$H��H�|$�{��H�M H�YH����E1��f�H�[H��tsH�M L�3A�F��H�4@H�L�<�I�vL���"�����u�I�A��H�x�h��L��H���cx��H�5��L��I��I�L��H�P1�襕��L����c��H�[H��u�E���H�L$L��H�5�H�=�1�L�5ܯ��r��H����z��I��觔��H��H��H���9l��H���Q���H�D$I��H��tE@H��H��M�/�l��H��躀��H��M��L��jH��L���1�L������M�XZM��u�L���7c��H�5 L'H�|$����H�E H�5����H�x����H�} H�GH�� H�G�蕃��H��$dH+%(u.H��(1�[]A\A]A^A_�fDL���`i��H�|$�Vi������ff.�@��UH�����H��H���(k��H��H�@ H�x ���H�} L�GM��t+L�����H�} H�GH�� ���H��]�E���DH�� ]�ւ��fD��ATI��SH���m�L��H���j��H��H�@ H�x �~��H�C H�8�Fx��H�{ A��H�� 膂��H��D��[A\�ff.���ATI��SH���
�L��H���Rj��H��H�@ H�x �2~��H�C H�8H���2���H�{ A��H�� �"���H��D��[A\����ATI��SH����L��H����i��H��H�@ H�x ��}��H�C H�8H��0�r{��H�{ I��H�� ���H��L��[A\����U�V�H���N�1�H��1��Ba��H��]H���i��fD��H��L'H��tH��L'��UH�-�L'H���y����uH�mL']�����H��H������H�QL']����H��K'H��tH��K'��UH�-�K'H���x����uH��K']��k���H��H��蠉��H��K']����UH�����H��K'��K'����H��P����H��H��H�E H��
H�EH��GH�E(H��H�E0�=���H��E1�E1�jH��1ɺjH�=A�1�j�r��H�� �CK'膼��A��E1�H�0�H��H�5��H�=+�� h��A��1�H��H�5Q�H�=u�H��J'�'c��A��1�H�x�H�5��H�=��H��J'��e��H��J'�r���A��H�m�H�5ԜH��H�=^�����H��J'�S���A��H�^�H�5��H��H�=�����H�IJ'����H�m�H�5��A��H�=��H��豑��H��]H��I'H�J'�5r��DH�5J'H���ab���N���ff.����ATI��UH��H��dH�%(H�D$1�H�$���H���	H��H�EH��tH;0tH���������L��H�����I��H��t3H�E8E1�E1�L��H��H�^H�5�L�`8��u��L�������urH�}8t;��`��H�<$����v����u"H�E8H�x@H�@@H��t
�x]���H���h]��H�<$H��t��b��H�D$dH+%(u[H��]A\�f�L�%�H'�PH���e��H��L���Qz��H�}8�k����@L�0�H�
���+1�H�5@������y��f.���UL���C���H��H���Xe��H��H�@8H��t�H�5ZH'1�1�]�m���L�m�H�
B��1�H�5Е菌��ff.�@��ATUH��H������H��tHH��H�EH��tH;0tH�������t+L�%�G'H��P�d��H��L��]H��A\�Ey��DL�0�H�
���1�H�5@����ff.�@��UH��SH���>���H��H���Sd��H�x@H���[��H�{H�[��H�=WG'�P譎��H��H�@0H��[]��ff.���UH�o H��u
H�G@]�@�w��H��H����c��]H��鏁��ff.�@ATUSH��H�����H�{PH��H����t��I��H��t
L��[]A\�f���f���H��I���kk��H�{PL��H���l��L��[]A\�@ATUH��SH��H��@dH�%(H�D$81��}���H�X+]@��v7��f�Hi�%I�$H�� )������*��Xf/���wi�E4��t"H�D$8dH+%(��H��@[]A\�D�,H��H�5YO�$}���E4��H�D$8dH+%(ueH��@H�5�[]A\����H�uPH�\$L�d$H���m���H�D$���Y�1�L��H���nt����uڋE4���U����r�����v��D��ATUH��SL�bHH��M��t9�$���H��L��H��4�ׂ�CH��H��H��?H��H)����H�{HH��t�pY��H�SH1�H��1�H�52��Yz���ԍ��H��4�ׂ�CH��H��H��?H��H)�H�S@[]A\���ATI��USH�����H�{PH���r��L��H�����H�{PH���r��[��\E]A\�,�����USH��H��dH�%(H�D$1��.���H����H��H�H��tH;0tH���]|������H�k H��ulH�C(H��t#H�T$dH+%(�H��[]��H�{8tH���q���H��H��t
H����n����ttH�=���v��H�C(�f.��;t��H��H���`��H��H�D$dH+%(��H��[]����fDH��H�5��1��Z��1��I���@H�k0H��1�H�5"�H��1��x����z��H�4$H���=m���PH��H�C(�`��E1�E1�H��H��H�9���H�5���}o��H�C(����_t��ff.�@��UH�o H��t"�]s��H��H����_��]H���ic��f��;���H��H��t�x��H��t	]���H�5;�H�=[�]��z�����UH�o H��t"��r��H��H���R_��]H��陊��f�1�]�ff.����1�H� ��Ð��AUA��ATUH���]��I���q��L��H���^��D��H����s���p��L��H����^��H�5�H���o���H��A�H�
��L��H�5���@���H���x�����uL��]A\A]��u��L��H���^��H�5N�H�����L��]A\A]���AWAVAUATI��H��UH���q��H��tp�PH��I���@^��L�=�H��L���މ��I��H��tvI�D$8H�x H��tHL��H�5��~��H�-A'L��P�]��H��H��]A\A]A^A_�r���]A\A]A^A_�fDL���H�
���H�5r��1�����z��H��I���`l��L��H��I�D$8H�x0�z��L��H���]��H��PI���q]��H�
�='L��L��H���W���1�������G��t�������AUATUSH��H��H�o8�z��H�} H���]��H�5�H���7e������L�c M����譅��L��L�-��H����\��L��H��I���
j������L�-�L��L����i����uoL�����H��H��tH�5��賈����u?H�EH�}(L� t1L���	k��H��t$L���la��H������H��[]A\A]��H���[]A\A]�L��L���%b����������ff.����G����H�G8H��tH�x�z_��f.�1��ff.�f���UH��SH��H�������tNH�C8H��t5H�XH��u
�*f�H�[H��tH�;�o[��H9�u�H���[]�fDH��1�[]���{�t�H���q��9C��H����[]�f.���ATI���"�L��1�A\H��H�5��1���R��f.���ATUSL�g H��L9�t!H��H��tH���R��H�k M��tL���[R��H�{HH�CHH��t�%R��H�{ tH�����H�����H����_��H�CH[]A\�ff.���AVAUI��ATI��UH��S���d�H��H���yZ����t\H�EH�8��\��H��I�$H�8�\��UL�
��1�PA��1�H�
��A�t$H�ß�S�\��H�� []A\A]A^�L��I���]b��[L��]H��A\A]A^����f.���H�G8E1�H��t(H�x8H��tH��获��E1���A��H��D���DD���@��AUI��ATI��UH��S��H���r�H��H���Y������H�lj�H��Hc�H�>���H�w H����H��L��[]A\A]�ao����wH��L��[]A\A]�Kp���3���H��L��[��]A\A]�of������H��L��[H��]A\A]�^f��fDH�G8H��tkH�p ���fD�[���H���p���H�EH�8�[��H��I�EH�8�[��UL�
ڎ1�PA��1�H�
J�A�uH���S�hZ��H�� H��[]A\A]�f���H�G ����AUATI��USH��H��H����f��H��H�C8H��H�x(豅����u
H��[]A\A]�f�L���f��I��H��t�H��t�L�k8I�}(I�E(H��t	�1O��L�k8H���_��L��H��I�E(H�C8H�x0�>t��H���t��H��H���W��H�5�H��H�C8H�x �$x��H��H��[]A\A]��N��f���H�vHH�H�t��ff.�@��H�
:'H��tH�:'��UH�-�9'H����f����uH��9']��[���H��H����w��H��9']����SH���#{��H��9'��9'����H��H�C0��H���f���SE1�E1�jH��1ɺjH�=�1�j�!a��H�� �W9'�2���H��E1�E1�jH��1ɺjH�=Ȍ1�j��`��H�� �$9'[�fDH�59'H���Q���f���@��UH��SH������H��H���#V��H�XH�;��z��H�{�z��H�{�z��H�{H�5J6'�j���{ ��u&H�=�8'�P�]���H��H�@0H��[]��fD�C �����f.���H�U8'H��t��H���/���1�H��1��3M��H�,8'H������ATI��UH��H��H���7�����t&��t=��tYE1�H�
���1�H�5���v���5�7'H��L��H��1�]1�A\��\��L���(L��1�H��H�EH�8�]����DH�EL��H�8�!s���ff.�@�G9�tQATUH��H����u��tE�u���H��H���2���L�%�7'H��P�T��H��L��]H��A\�i��@��H��H�-�H�5�1�]A\�N��@H�G8H��t/�t!�@��u
1��c�����V���fD��H�U�H�5z�1��3N����UH�z8t<H���l`����uH�E8H��@]�f.�H�E8H��h]�o����L���H�
���1�H�5X��{�����AUATU1�SH��HdH�%(H�D$8H�GH�\$I��L�d$H��H�0�v^���@H�t$H���3Q��H��L��L��H���2e����u�H�5G���H���_W��H�T$8dH+%(uH��H[]A\A]��og��ff.�@��ATUS�Z��H�I��H��H��u!�41�H�������}����uH��H��H�;H��u�H�]H��H��u�L��[]A\����H�5'H��tH�5'��UH�-5'H���b����uH��4']��+���H��H���s��H��4']����ATI��UH��H�����L��H���R��H��I������tH��]A\��L��H����H��I�������u��}��H��4�ׂ�CH��H��H��?H��H)�I�T$�ff.���ATH��H�5�I��UL��H��dH�%(H�D$1��H����uH�D$dH+%(��H��]A\�H�T$H�5ӇL��1��e�����H��H���*Q��1�H�ŋD$����;U8t��U8��vH�uHH��t�H�E@H��H�P�����|��H��4�ׂ�CH��H��H��?H��H)�H�U@�[����2e��f���UH��SH������H��H���P���x4H�Å�uAH�{(�H��H�{�G��H�{ ��G��H�=3'�P��z��H��H�@0H��[]��fD�@4�|}���f.���ATI��UH��SH�P�9a��H�}PL��H���*a��H��E1�H	�tH��tH��t#��\�D,�[D��]A\�A�[]D��A\�A�������f.���H�U2'H��t��H������1�H��1��SG��H�,2'H������H��1'H��tH��1'��UH�-�1'H���_����uH��1']��K���H��H���p��H��1']����AWAVAUI��ATUH��SH��H�T$�L$dH�%(H��$�1��d���L��H����N��H�xH����I�Nj�����wA�G|�������#
��H�|$L�t$P�x��I�H�T$8H�t$4I���s��I�H�t$<L����i��M���bL��L�d$`�v��L��L���D$�Ts��f��f���D$4�*L$`�=ԥ�*T$d�|$�\��L$8�\��D$4�L$8L�l$@L���R���D$<�L$PL���s���D$L���#\���fD�X��A�W|��A�G@��uL������	Шu�D$��I�L�l$@L�t$PL��L�d$`�r���=#��|$�-���������=�L�t$PL�l$@L�d$`�|$L���R��L��L��L�����D$P�D$`�R��f��d$P�l$`�A*��/5���L$(�(�w5�<�/��v+�X�(�(�(��^��^�/{��^�v	/r�w��H,��H,ʼnʉ�A;G uA�G$9���A�g@�(�I�(�L$�E����L$�����H,D$`I�A�G �H,D$P�A��A�G$������S��H��I��H���L��M����H��$�dH+%(��L��H�ĸ[]A\A]A^A_�r��I�H�T$PH�t$@D����`��I�hD��E��D��D����R��A�wx����I�h�m��H�5�I��H���u��L��H���L���D$@H�t$`L��H�D$`�D$h�D$P�D$l�i��M��tL���q��A�|�|H��$�dH+%(��H�ĸ[]A\A]A^A_��A��I�h�>E���Q���f��=���L$8L�d$`�D$4�|$�|$����fDI������d$P�l$`�H,��H,ʼnʉ�A;G �<(�I�`�L$�Z����L$������A�|�d$P�l$`��H,�I�H�T$<�A��H�T$(A�G �H,�A�G$H�D$8H��H�D$ �:_��I�h��A�؉ى��(Q��A�wx����I�whI�`�[��H�5��H��H�D$�Kt��H�t$H���^J���D$8H�|$L��H�D$`�D$h�D$<�D$l�`g��A�G$f���A��f��H*�A�G �A*���^��^��H,�f���H*��^��H,��uO��H�59�H��H���s��H�|$H����I��I�pH�D$P��U��f�I�p���H*��D$X�\��f�L��H�߉��H*��D$\�f��L�D$A�O@M��tL���o��A�G|�������rH�T$ H�t$4L����X��H�t$(L��L���@N��I�wPI�H�CZ��H�5�I��H����r��L��H���I��A�G f�L��H�D$`L���A���H*�A�G$�^��D$hf��H*��^��D$l��e��H�|$�D��H���\��H�5MI��H���wr��L��L���H���,L$P�D,L$<�,T$8�D,t$4�L$D�L$�T$��D��L��H����G��H��E1�D���L$H��QD�L$1ɋT$ �a��ZYM��tL���Dn��M��tL���7n��A�G|��u.I��������N��H��I��H����G��M��t
L���n��DH������H��$�dH+%(��H���/����A��I�h�VA�������L�s~H�
2��f1�H�5�}�n���A.���^����X���I�(�M���A.���/����)���I�`����A�|�J����H,��H,�9���D$I�H�L$�X����L$�������d$P�l$`����f�A�G$9�������v�����H,��H,ԉ�A9G u�A�G$�I�w0�D$uA�G@�I�(�W��H�52�I��H���_p��L��H���tF��A�G f�L��H�D$PL���A���H*�A�G$�^��D$Xf��H*��^��D$\�Yc���T$L��(��A^��(��i��L���V��H�5o|I��H����o��L��L����E��I����L��L��I��H����E��M��tL����k��A�O@M��tL����k��M�������L���k������H����f��H�5W�I��H���\o��L��H���qE��A�G f�L��H�D$`L���A���H*�A�G$�^��D$hf��H*��^��D$l�Vb��M�����������I�H�(�������Y��E1�H�
���w1�H�5{�k��fD��AUATI��UH��SH�����H��H���LD��L�-�&'H���[��L��H���n��H��L���H�{8H�C8H��t� g��H�{(H�C(H��t�j;��H�{XH�CXH��t��f��H�{HH�CHH��t�>;��H�{pH�CpH��t��f��H�{`H�C`H��t�;��H���B��H�CH��[]A\A]�ff.���AUI��ATI��UH��S��H�����H��H���WC����tj������t|H�EH�8�E��H��I�$H�8�E��UL�
[y1�PA��1�H�
�yA�t$H����S��D��H��([]A\A]�D���H��L��[]A\A]�m��fD���H��L��[]A\A]�pY���p|H��L��[]A\A]�Y��ff.���H�����1�H��H��1��#:����SH�����H��t.H��H�H��tH;0tH���v]����t���[�f.�H��yH�5�1��K<�������[�@��ATUH��S���n�H��tyH��H�EH��tH;0tH���]����t\9��tt�e@�H�}���t�.A��H��H���A��H����E��L�%$'H��P�A��[]L��H��A\�(V���[H��x]1�H�5>�A\�;���[]A\�ff.���SH����H��t.H��H�H��tH;0tH���V\����t���[��H�gxH�5��1��+;��f�[�D��ATfA~�UH��H���H�H����H��H�EH��tH;0tH����[����trfAn�.��ztR�e@�H�}D���t�?��H��H���@��H���D��L�%�"'H��P�d@��H��L��]H��A\��T��f�H��]A\��H��H��wH�5��1�]A\�T:��@��SH����H��t&H��H�H��tH;0tH���&[����t
�C|[�DH�?wH�5
�1��:�������[�ff.����ATUH��S����H����H��H�EH��tH;0tH���Z������9]|���e@��]|��tHH�}t��>��H��H���Y?��H���C��L�%�!'H��P�=?��[]L��H��A\��S��fDH�}XH�EXH��t�"b��H�}HH�EHH��t��l6��H�}u��[H�Fv]1�H�5�A\�9���[]A\�ff.���AVI��AUATI��UH��S����H��H���>��I��tq�������H�EH�8��@��H��I�$H�8��@��UL�
�t1�PA��1�H�
�tA�t$H�΃�S�#@��H�� []A\A]A^�fDL���8K��[L��]A\A]A^�����L���:��[L��]��A\A]A^����fDL���H8��[L��]��A\A]A^����fD��H��'H��tH��'��UH�-�'H���@M����uH��']�����H��H���@^��H��']����AUATUH��SH�����H��H���?=��I��Hcm'I��<��H���>��H���L�+M��t2�`��L��H��I���=��H���i��H�;L����<��H���uL����uH��[]A\A]�fDH��H��[]A\A]�G��ff.���ATUH��H������H��H���<��I��Hc�'M�$M��t�_��L��H���o<��H���gi���B;��H�=�'H����f��H��H���H��]A\����UH���s���H��H���(<��I��HcV'I�,H��t�_��H��H���<��]H���h��@]�fD��ATI��UH��H������H����H��H�EH��tH;0tH����V����t|Hc�'L�dM��t�_��L��H���;��H���[a����uH��]A\���S:��H��H��I���e;��H���I����t�H��L���N;��H��]H��A\�F���H��H���H�5�1�]A\�L5��ff.����AWAVA��AUA��ATE��UH��S��H��(dH�%(H�D$1�����H����H��H�EH��tH;0tH����U������Hc�'H�D9hu
D9p��D�hD�p�XD�`�c9��H��H��I���u:��H���=Z����uQL��H��\$H�$D�d$�M:��H��H���4��H�D$dH+%(��H��([]A\A]A^A_��L��H���
:��H���e:��E���D��H��D���2Q���9X�P���D9`�F����H�D$dH+%(u#H��(H��H�5v�1�[]A\A]A^A_��3���N����ATUH��H������H��thH��H�EH��tH;0tH���T����tK�F8��H��H��I���X9��H���G����uH��]A\�@H��L���59��H��]H��A\�D��fDH��H�uH�5��1�]A\�43��@��UH���3���H��t>H��H�EH��tH;0tH���T����t!�7��H��H���8��]H����a���H�	H�5�1�]��2��f.���H������1�H��H��1��#0����H�}'H��tH�q'��UH�-`'H����G����uH�M']��[n��H��H����X��H�1']����UH��SH�����H��H����7��H�x`H��H�@`H��t�J/��H�{x�A/��H���H��'�J:��H���Hǃ�H��t�/��H���Hǃ�H��t��.��H���Hǃ�H��t��.��H�{H�.��H����.��H����.��H����IN��H�=Z'�P�a��H��H�@0H��[]��f���UH��H���H�t$(H�T$0H�L$8L�D$@L�L$H��t7)D$P)L$`)T$p)�$�)�$�)�$�)�$�)�$�dH�%(H�D$1�H�=�'t3H�nH�5Յ1��0��H�D$dH+%(ucH���]�DH��$��$H�D$H�D$ �D$0H�D$����H��H��H���T��H�����H��H���6��H�'��vJ��fD��H�'�@��L�G`H�G`M��tL���C-���ff.�@��AUATUH��SH��H���7���H����H��H�EH��tH;0tH���P��������J����tH��[]A\A]�f�H����8���Hc�I���-��I��H��t+H�H�[H���
f�H��Jf�H��J�Rf�H�f�P�H��u�H�uXH��u\H�}@L��D���MN��L��H�EX�A,��H�}0�X��H�uXH��[H��]A\A]�M��f�H��H�RlH�5ރ1�[]A\A]��.���H�}@�Wa���D��ATUH��SH�� dH�%(H�D$1����H��H���q4��H�D$H���B��H���h[��H�C(H���T��H���dM��H���|@��H��H���14��H���,��H����W��H��I���FE��1҃����C ��	ЈC �z}H�
rqH�tzH��H�5�k�~����H�C0�@��H��H����3��H�{0H���H�H�{0�dH�L$H�T$H�5nzH��H���?�����}H�D$dH+%(uhH�� []A\�f�L���M��H�=kH���G��H�=kI���G��H�SH�sH���H���u9��H���A��H�5�jH���^.��H���������~G��H�D$H��y� 1�H�H1���4��H�|$��/��H���N*����tJ��@��UH��H��dH�%(H�D$1����H��1�H�5�jH��1��	K��H�<$�*��H�<$H�D$dH+%(u
H��H��]���F��f.���UH��H��dH�%(H�D$1��?���H��1�H�5*jH��1��J��H�<$�)��H�<$H�D$dH+%(u
H��H��]��jF��f.���UH��H��dH�%(H�D$1�����H��1�H�5�iH��1��)J��H�<$� )��H�<$H�D$dH+%(u
H��H��]�_�E��f.���UH��H��dH�%(H�D$1��_���H��1�H�5JiH��1��I��H�<$�(��H�<$H�D$dH+%(u
H��H��]��E��f.���AUI��ATA��UH��SL��H��dH�%(H�D$1����H��1�H�5�hH��1��;I��H�<$�2(��H�<$H�D$dH+%(uH��I��L��D��[H��]A\A]��D��ff.���UH��H��dH�%(H�D$1��_���H��1�H�5JhH��1��H��H�<$�'��H�<$H�D$dH+%(u
H��H��]��D��f.���AUA��ATA��U��H��dH�%(H�D$1����H��1�H�5�gH��1��@H��H�<$�7'��H�<$H�D$dH+%(uH��D��D���]A\A]���D�����UH��H��dH�%(H�D$1��o���H��1�H�5ZgH��1���G��H�<$�&��H�<$H�D$dH+%(u
H��H��]��C��f.���H��dH�%(H�D$1�����H��1�H�5�fH��1��]G��H�<$�T&��H�<$H�D$dH+%(u	H����2C��f���AUI��ATA��UH��H��dH�%(H�D$1����H��1�H�5�fH��1���F��H�<$��%��H�<$H�D$dH+%(uH��D��L��H��]A\A]�K�B��fD��H��dH�%(H�D$1��#���H��1�H�5fH��1��}F��H�<$�t%��H�<$H�D$dH+%(u	H����RB��f���AUA��ATA��UH��SD��H��dH�%(H�D$1����H��1�H�5�eH��1��F��H�<$�%��H�<$H�D$dH+%(uH��A��D��D��[H��]A\A]����A��ff.���AUA��ATI��UH��H��dH�%(H�D$1��%���H��1�H�5eH��1��E��H�<$�v$��H�<$H�D$dH+%(uH��L��D��H��]A\A]��FA��fD��UH��H��dH�%(H�D$1����H��1�H�5�dH��1��	E��H�<$�$��H�<$H�D$dH+%(u
H��H��]����@��f.���H��dH�%(H�D$1��C���H��1�H�5.dH��1��D��H�<$�#��H�<$H�D$dH+%(u	H�����r@��f���UH��H��dH�%(H�D$1�����H��1�H�5�cH��1��9D��H�<$�0#��H�<$H�D$dH+%(u
H��H��]��
@��f.���UH��H��dH�%(H�D$1��o���H��1�H�5ZcH��1���C��H�<$�"��H�<$H�D$dH+%(u
H��H��]�o�?��f.���UH�����H��H���+��]H�x0��`ff.���USH��(dH�%(H�D$H�G8H�����@uuH�X1�H��u�D�H�[H��t3��=��H�;H���*��H���_;����u�H�3H���p(��H�[H��H��u�H�D$dH+%(u~H��(H��[]��0��fDH��H�<$�D�H���<YH���K��H���S��H�k8H��H�5nl��H�D$H�}�/��H�EH�C8�`��:���H�D$dH+%(u	H��(1�[]��Q>�����AWAVAUI��ATA��UH��SH��XdH�%(H�D$H1������t+H�D$HdH+%(��H��X[]A\A]A^A_��L�����I��H����H��H���,��H���m�S�H���KXI���C�H���;XH���J��H���R��H��H�D$��(��L��H�D$�AA��D��L�����1�����LL��� ��H���\/��H�D$H��t.H���L�3I9�tL���(��H9D$�H�[H��u�H�|$�V��H��H�D$(�i(��H�T$0H�5�j��H��H�D$0H�D$(H�D$8�F5��H�t$(L���9:��H����.��H�|$(H��H�D$�U��H�D$(H��t.I��fDI�H���=����������M�vM��u�H�|$�yU��H�|$H�D$H9���D��H����5��M���7���H�D$HdH+%(��H��XL��[]A\A]A^A_�(U���H���h!���fDL����'������H��tkH�(H��H����*��H�������f�H�|$��T��H���T���H������H��A������D��L������/����HE��)���f�D��H���UG���5���1�1��g*��H��������^����t;��@��AUATUSH�����H��thI��H��E1��H�;������~Hc�L��H���*��H��t)H�[H��u�L���T��H��L��[]A\A]�f.�L��H���u$��I����H��E1�[L��]A\A]�ff.�@��AUATI��UH��SH��H�G8H��t#H�x�)��H��tH��[]A\A]�f.��PH���&��H���[5��H�E8H���f�HL�����H�]8H��H�{��#��E1�H��L��H�CH��j��H�5�]��R��E1�H��L��H����H�5�]�R��L��H���K��H�]8H�{8t`L���2����uH�E8�@H���$���E�����E�����PH���U%��H����%���5_'H��1�H��1�[]A\A]�-���H�{@u�H�{(t�H�{1���K��H���3��I��H���s����3��H��H�C@���H��H�S(L��L�C@H�{0P�L�
��}���ZY�6���fDL���8�����G����u1�L����?���5���fD��H���SH����E��H�}8I��t)L��\H�
;v��1�H�5U��K����H�I��E1�E1�H��H�E8L��H��H������H�5g\�3��1�1��H�CH�]8����H�C0H�]8H�{0t�pH��H�C H�E8���L�kH�
�u�1�H�5�T�?K��ff.�@��ATUH��H���}���1�H��1��A��H��I���/��H�=�[��1��-��H��L��I�D$@����H��L��]A\�ff.���AUATUH�G8H����H��H�xI���{&��H����L�m8L��I�}�R9��L��I�E�/������H�����L�m8I�}��H��L�
�g��E1�1�U1ҾL���)��1ɾE1�L�
���1�L��H�,$�x)��Y^L;e0��L������5�'H��1�]1�A\A]�M*��DH�E8�h�m���]A\A]�f�H�E8�S�H���KQH���C��A�U����L��ZH�
�s�21�H�5�R�I��fDH��1�L��E1�UL�
,c��1ɾ�(��H�}(H�E0H�E(XZH��t�@��L�-�'H��P�!��H��L���Q6������@L��SH�
Js��1�H�5HR�I�����A�U�����I�uH����I�}8I�E8H��t���I�}@H��t��#��I�}@I�E@H��t���I�} I�E H��t���I�}0I�E0H��t�p��I�}(I�E(H��t�:��L���2�����DI�EH���1���]�����UH��SH��获��H��H��� ��H�x H��H�@ H��t���H�{(H�C(H��t�����f�H�BH��H�0����H�S8H��u�C��t!L��gH�
�q�l1�H�5�P�G��H�=I'�P�J��H��H�@(H��[]����AUATI��UH��H����2����t5H�E8H��tH�x�tH��]A\A]���H��1�]A\A]�����H�������tL���L%���E]A\A]���H���N�H��I�����L����?��L����)���ff.�@��ATUSH���0��A��1�A��t[]A\��<��L�%�WH��H�C8H�x �$��L��H��H���F'����uRH�C8H�XH��t4DH�+H��� ����t���H����PH����j,��H�[H��u�[�]A\��L��H���)��H��u�1�L��H���6����T���f���USH��H��H�z�*&��H����A��H�{H���f��H�{0H9�tMH��tH�c����tH��[]��H���(P�ƅ�uH�{0�)(����H�{0H��[]�"��������t�H����O�ƅ�uH�{0��'����H�{0H��[]��E��ff.�@��AUA��ATI��U��SH��H����D���H���UQH���]F��H�{ H�����H��L��H���(��H��H��[]A\A]�������AWAVAUA��ATI��U��S��H��(L�D$�J�I�|$ H�$�Z�5�D���H��I����PI�Ņ�������L�����1ҾH����#��I�|$ �t$1�P��L�b���PH�Xb��L��j�jPL�L$0�6��H��0A�ą�yL���#��H��(D��[]A\A]A^A_Ð���9I�����I�|$ H�5U�o"�����f���L���?NH��H��������H��H���S��H���{���I��H����H���':�������1�H�D$fDH��L�����H��H���H�=�T����H��H���BB������1�H�5~TH���$��I��H����H��������}H��1�H�5VT�|$��H�D$H��H��t_1��X<��H�0I��H��t#1��CL��I���+���CI�4�H��H��u�L�����L�����H�|$���H������2���1����L�����H�����H��H;\$�����H�c��1�1�������1��I�����1��9����L��A��bK��H��H����������:������f.�H�qb�1�1�������fDH�b�1�1��k���e���fD��AWAVAUA��ATA��UH��H��dH�%(H�D$1��A�I��E�����E��tO��tp��t$E1�H�
m�;1�H�5�J�A���I��1�D��D��M��H��H�$�r�����tnH�D$dH+%(��H��]A\A]A^A_�@H�D$dH+%(��H��D��H��1�]A\A]A^A_��f.�H���hKA���;���H��船��H�5RH�=8LH���2>��H��H��1���"��L��H��H�$H��H�P�OJH���w��L���6���<����-��D��1Ҿ���������AVAUATUH��L�o M����H��A���A��L��L�-�NH������H���@��L��H����D������L�m8M��t/�~5��I�} H�����I��H��tL�5�NH��L��� ����u$H��D��H��E1�]1�A\1�A]A^����L��L���E#��H��u�H��L��L��1�]A\A]A^�0���H��D��L��H��1�]A\A]A^�5���DH��H��PH�5�j1�]A\A]A^�����AWH��AVAUATUH��蘸��A��1�E��t]A\A]A^A_�@H��蘳��I���@
L��H���
I�ĸM��t�H��� ���I���+��L��H��H���j��H��L��I���\��L��I���>��L�����>��9�t]�A\A]A^A_�@L����/��L��H����/��H��H���B4����u�L������L��H������H��H���D����u�L����@��L��H����@��H��H���^D����u�L���r��L��H���g��H��H����3�����a���L����A��L��H����A��H��H���D�����;���L����+��L��H���+��H��H���3��������L����<��L��H����<��H��H���J+���������o���ff.�f���AUATI��UH��SH�����I���L��H���TH��tH��[]A\A]�@L�����H��H��t�H�3H���;��H�[H��u�H��[]A\A]Ð��ATUH��SH�_H�{�&��I��H��tL��[]A\���H��H����I��H��t�H������H��I���E���H�{L��H������L��[]A\�ff.���AVAUI��ATUSH���i���H��H��t[H��]A\A]A^�DL�5y�&H�-�M�
I�nI��H��t�1�H��L��1��2��H��H��I������L��H�����H��t�[H��]A\A]A^�f.���AWAVAUATUH����H��L�=dM1�I��L��H��1��>2��L��H��I���0���L��I�����M��t]L��A\A]A^A_�H��H������)���-H�54oH��H�����1�L��H��1���1��L��H��I������H��I���B��L���:��]L��A\A]A^A_�DE1�]L��A\A]A^A_����H��t/UH�GH��H�x�$��H��H��tH��]���D1�]�@1��D��AWAVAUATUH��SH��dH�%(H��$�1�L�d$���H��H���@��E1�1�1��@4H�x1�M��H�D$H������H���/H���g/��H��H��I�����H����5��H��I���V
��L��L������H����0��L��H���8
��1�L��H��H�5A[�$����upH�|$��H���
��H�D$H��t#H�H1�H��Z1����i��H�|$�O��H��$�dH+%(��H�Ę1�[]A\A]A^A_�@1�L��H�5!KH��������t���H�sPH�\$0L�|$ H��L�t$(�)��f�L��L��H����#�����BH�t$(H�4$�,���H�4$H��� ���H��t�1�L��H�5�JH���'���������H�T$(L��H�5�FH���xW�������H�D$ H�T$`�'H��H�$��1-��H�$L��H��H�5�C�;W���������H�D$ H�=XJ�p1�����L��H�5�CH��H��H�D$�W��H�|$�$�f���$���[���1�L��H�5JH���g��������:���f.�H�D$�D���fDL��H������E1�1�1�H��1���������1�L��H�5�IH�����������L��1�H�5�IH������������$��ff.���AVI��AUATU1�SH��@dH�%(H�D$81�H�\$L�l$�|���I�vPH��I���=��D1�L��H���"����t/H�t$L���J���H��H��t��
��H��H����
��H����DL��H�5���H���V��H�T$8dH+%(u
H��@[]A\A]A^��$��@��AWAVI��AUATUSH���'��M�fH�D$A�D$$A�D$ ��uL��H�5U����	��
��A�D$ M�fI�|$�$����H���7H��H������7��I��L�;L����,��L��L��H�����H����	��I��H��tDI�|$H�����H��tL��H���
,����u#H�����L��H�����I�|$H��H���S��H�[H��u�I�F1�H�5����H�x����I�FH�\$H�5d���H�8H���6��1�H��H�5�I���.��H�߾�9���5�&L��1�H��1�1�[]A\A]A^A_�	��f���AU1�1�ATUH��SH��Hc��&L�%]�&H�L��H�_1��|��L�-��&L��1�L�%�&H�L��L���[��H��&L��L��H�CH���B��H�C�YH��E1�E1�H�5DH��H�����I�����H��H��L��[]A\A]���f���H���H��H���g���ATUSH��H��dH�%(H�D$1��<��H�e�&H�56�&H�=O�&I��H�����H�CP��E1�E1�H��H��H�����H�55F�t���?���E1�E1�H��H��H�����H�5C�P��1�1�����jL�
F1�jL��1�L�aUH��H���'��E1�E1�H��H�C H���H�5�EH�����H������1�H�T$1�H�C8H�5�EL����$��H�|$1�1�H�5�E���H�|$H�����H���y0��H��H�C�m��H���eT��H�=�E���H��E1�E1�H�C(H��H��H�5�E�f��H�{(H�5�E���Y^��uV�C<��u�k<H�D$dH+%(uWH��[]A\�H�{HH��t�
���{4H�CH��t��C4�8���D�S<��u���H��1�H��������������UH�5�DSH��H��H�z(�����Ņ�u5�C<��tH�{HH��t����{4H�CH��u9�k<H��[]���S<��u��|�H��1�H��菨���k<H��[]�D�C4��7���f���ATUSH����.���
��&H���&���;H�߾P�5��H��H�3E1�H��H�:E1�1�H�E H���H�="DH�EH�C��H�E01�j@j@jjj���H��(H�3E1�jE1�1ɺjH�=�Cj��&1����H�� A��H�
�CH��CH�5�C���&H�=�C���H��1�A�h�A����H��CH�5�CH�=�CH��&�w,��A�1�A����H��CH�5�C�$�H�=�CH��&�A,��H��&�u'��A��H��CH�5�CH��H�=�C��3��H���&�1��A��H��CH�5�CH��H�=w;�3��H���&�'��A��H��QH�5�CH��H�=�C�t3��H�]�&�x!��H�5�CA��H�=�CH��H���I3��H�:�&�-,��A��H��QH�5�CI��H��H�=�C�3��L��A��H�sCH�5�CH�=�CH��&��2��L��A��H�}QH�5^CH�=hCH���&��2��H���&��A��H�TCH�5iCH��H�=@�2��H���&�h��A��H�CQH�5ICH��H�=HC�e2��A��1�H�VQH�56CH�=,AH�q�&����A��1�H�]QH�5CH�=%CH�P�&����A��1�H�dQH�5CH�=�@H�/�&���H�+�&�.��A��H��BH�5CH��H�=C�1��A��@1�H�<QH�5�BH�=	CH���&�R��A��@1�H�KQH�5�BH�=	CH���&�)��H���&�4��H�=�CA��H�aQH��H���>1��H��B1�H�5�BH�=�BA��H���&����H��H�v�&XZH���&[]A\���@H�5y�&H��������ff.����AUI��ATI��UH��S��H��dH�%(H�D$1����H��H��������H��TH�lj�Hc�H�>���H�D$dH+%(�|H�wPH��L��[]A\A]�&��fDH�D$dH+%(�LH���H��L��[]A\A]���H�D$dH+%(����H��L��[]A\A]���@H�D$dH+%(�������@H�D$dH+%(��H����{���H�D$dH+%(��H����(���H�D$dH+%(��H�������H�D$dH+%(upH������@H�D$dH+%(uPH�wx�����H�D$dH+%(u0H�wp����H�0�#��H��H�D$dH+%(��������H�0�+��H��H�D$dH+%(�������fDH�D$dH+%(u�H�w�r����H�0H�t$H���_���4$L�����H�D$dH+%(u�H��[]A\A]�f�H�0H�t$H������t$L���c��뽐H�D$dH+%(�<���H�w8���H�D$dH+%(����H�w0����H�D$dH+%(�����H�w(���H�D$dH+%(����H�w ����;�H��H�D$dH+%(�<�������H�EH�8�L��H��I�$H�8�=��UL�
91�PA�I1�H�
*?A�t$H�GH�S���H�� �����AVI��AUI��ATUH��S�����H��H���y��I���w3������uML�����A9�$��[]A\A]A^�fD��u#L���K���[L��]H��A\A]A^��H�EH�8�d��H��I�EH�8�U��UL�
'81�PA��1�H�
B>A�uH�`G�S���H�� []A\A]A^��H�xHH�@HH��t���L�����H�����H�����I�D$H[]A\A]A^�DL�����A9�$�����A��$�H�5;�&�f�A��$�H�5�&[H��]A\A]A^�������ff.����ff.���H�u?H�GH��H�GH�o@H�G H�dH�G(H�iAH�G0�@���ff.����ff.����ff.���Hc)�&H�H�G �ff.�f����ff.����ff.����ff.����ff.���Hc-�&H�H�G0�ff.�f���Hc��&H�H�G�@��fD��I��H�~L���]ff.�f�����H�z0�����������H��H�~E1�E1�V1�1Ҿ�_��H���f.���9�}1��DH������1��y"��1�H���f�SH���G��H��1�������H�M�&H��E1�1�E1�L��A�H�t�D��H��H9�t,�A�@�u� u����H��A��H9�u�fD�f��f��9�O�A9�DO�A9�DO��A*��Y
;s�A*Ѹf�9�OȍD
��*���X��
s�X�f/�wf/
s�
v[�@A���J����H,�[�f��1�f(��@��UH��H��H��:H�5VH��dH�%(H�D$1�H�L$�i����t�L$��uH�D$dH+%(uJH��]��H�����H��H��t H��1�E1�E1�j�t$1����XZ�H�����H�������ff.���LJ������uhATUSL���M��tKHLJ�L��f.�I�,$H�}�UH�EH��tH�}��H���~�H�[H��u�L���*��[1�]A\�fD1��ff.�f���ATI��UH��SH��H��dH�%(H�D$1�H�$��!��H��H������H��L��H��������t1H���H������H�D$dH+%(urH��[]A\�f�����H�<$����K����tH�<$�>����@��H�<$����#����u�H�$H�l\�1�H�H1���������ff.���ATI��UH��H��SH��H�� dH�%(H�D$1�H�t$H�D$�$��H��E1�1�H��H�D$A�L��PSjH�T$(���H�� ��u5H�t$H��H�D$�/���H�D$dH+%(uH�� []A\�fD�H�����������ff.����AWAVAUATUH���
H��I��I��I��M���6 ��H��H�EH��tH90tH���=������M���M��tG�"��H��I�$H��tH90t1L���	����u%L�>[H�
�n��1�H�5 7�4"��@H��L��L��L���_"��H�5H���H��H���]���H�����H��txL���h��H���&H��H������H�5O���H�����H��tH��]A\A]A^A_��]A\A]A^A_�fDL�2RH�
n��1�H�5{6�!���H�5�QH������t���@L��QH�
�m��1�H�5;6�O!��ff.�@��ATI��UH��H���J��L��H�����H��H��H��]A\�-#��ff.�f���ATI��UH��SH��H��dH�%(H�D$1�H�$�^��H��H���C�������L��H���3���H��H����"����t,H���H���U��H�D$dH+%(uTH��[]A\�@��H�<$���������tH�<$����@H�$H�eY�1�H�H1�������
��ff.��AWAVAUATI��USH��H��H��H�����H���H��H���	��H������b��H��I���w�H���L��H������M��tpL����H��tcL���;��L��I����
��L��I�����H�N�&L��L��H�����I��L��H��I��H�
m���H�����M��t'L���X��fDI��H�
���L��1�H���9	��H��H��[]A\A]A^A_��AWI��H��AVAUATUH��dH�%(H�D$1�I�����H�$H��I���2���1�L��H��I���b�H��tmH��H���2���L��I����1�L��H���:��L��I����H�����L����L���W�H�D$dH+%(ufH��L��]A\A]A^A_�fD���H�<$��������tL��E1��L���f.�H�$H�}W�1�H�H1�������
��ff.����UH��������&��u	]��H��H�5��&]����UH��SH���n��H��&�q�&��uMH��P� ��H���h��H��H��� ��H�F-H��H�h,H�CHH�}-H�C(H��[]�f�H�5�&H���A��ff.�@��UH��SH������H���&���&��uEH��P� ��H������H��H�����H�V2H��H��2H�C(H��[]�DH�5a�&H������ATH�=QM���I���z��H��L��pjH��L�
��A� H�
�������H��I������*�&XL��ZA\�f�UH�=M�C��H�����H���hH��jH��H�
�L�
,A�(�Q�ZY]�ff.�f�H��H�=�L����H��L�
�>A�`jH�ƺ��PH�
�����H���ff.�@ATH�=�L���H����L�
2}jH��A� �PH�
����XH��I����
���L�&XL��ZA\��ATH�=9L�B��H����L�
��jH��A� �PH�
� �V�� H��I���f
�����&XL��ZA\����ATUSH�/H��H��`H��t���H�C []A\���k��H���S��H����1�1��H��`���H�5TH��I���	��H��`L��1��C��L�����H��`1��m��H��`���H�C []A\����UH���������&��u)�4��H��H���)��H��H���]�f�H�5��&H�������ff.�@ATH�=�J����I���:�H��L�源jH��L�
2�A�0H�
�u���� H��I�������B�&XL��ZA\�f���UH��SH��Hc$�&H�H�{H��tH�sH��u7�R�H�C��H��H�����H��1�[H��]�j��f.�H�C����H�{�ff.�f���ATUSH�����H�y�&�k�&����H�߾P����H�eM�H��H�H3H�E H�mbH��H�EH��dH�E(H�$/H�E0�+��H��	H��I���H��
H��I���H��H�yI���H��H�mI����H��H�fI����H��H�dI���H��
H�]I���H��H�WI���H��H�QI�w���H��H�LI�c���1�A��H�EIH�5XIH�=bI��1�A��H�_IH�5qIH�=zIH���&�X�1�A��H�oIH�5{IH�=�IH���&�/�1�A��H�yIH�5�IH�=�IH���&��H���&�z�A��@H�vIH�5�II��H��H�=�I���L��A��@H�RH�5|IH�=�IH�G�&����H��H��&H�4�&�o���H�-��&1�1�H��H�3E1�E1�jH�=II1�jU���H��H�3E1�jE1�1�1�jH�=1IU���&1��j���H�� []���&A\�H�5��&H���9��Y���@ATH�=�HH�� dH�%(H�D$1����H��L�
��A��jH�ƺ��PH�
���"�H�D$H�D$ I��H�V�H�D$�L�H�T$L��H����XZH�D$dH+%(u
H�� L��A\����ff.�USH��H����H�?@H��t0�@H��������uH�nHH�5	a1���1��dfDH�����H��H��tmH����H�x���H��t`�H����t+���_��H����H��2H�Z@�q��R��u��H��[]�H�HH�5�`1��{�H��1�[]�L��GH�
d`��1�H�5�G������AUI��ATUH��SH��H�����H��H��I����H����H9�t)H��OH�5�_1���H��1�[]A\A]��H�59+H���!����t%L��H����H��L��[H��]A\A]���DH�AOH�5Z_1���띐��AUI��ATUH��SH��H����
��H��H���I�H��I����H9�t)H��OH�5�^1��T�1�H��[]A\A]��H�5�+H���q����t%L��L���r��1�H���D�H��[]A\A]�fDH�OH�5j^1����1��ff.�@��SH������H�D�&�:�&�����i�H��H�����H��2H��H�
��H���H����PH���H�������H��H�3E1�H�
f3E1��H�H0H�=�E1�1�jjj���H�� ���&[��H�5��&H���a��^���ff.��ATH�=�E�B���I����H��L��jH��L�
"�A�(H�
�����P��H��I���`���.�&XL��ZA\�f�AVI��AUI��ATUH��S�9H��tM���st?��i���x�-H��M1�E1�1��%�[L��]A\A]A^�f��yu�H�CH�N�xt��"L���)�I��H����H�{ L����H��ul� ��L��I��H�C�@fA�$��L��I�D$��H��I�D$��H�{L��I�D$����I�t$H�{ L����[L��]A\A]A^�L��1�H��M1���H�[L��]A\A]A^�@H�D���@A�}���������A�}���������AWAVAUATUSH��H�t$�G\t(I��M��I���v(H�J�1�1�H�M��H��[]A\A]A^A_�I��H�OP�����H��H�D$H�H9���A��1�A)�H9�DO�H�CPH�C@H�@H��tH�(�UJ�L"H��H�� v%� ��H�{@�H��H������UH��D�|A�M�P��H��H�|L��f�LL�����B�#�EH��[]A\A]A^A_�fDH�CPH�{ E1�H�m%H�5�B�2�H�t$H�L$H��H��A����H�D$�,���ff.�f�ATUSH��1����H�ń�t\L�%�B�8�H�EH�HH;MsRH�uH�M�H�UH�E��SH����t��"u�L��H��H���C�����u�[H��1�]A\����fDH�����H����������AWAVAUATUSH��I�xt1�H��H��[]A\A]A^A_�/���A�@I��I��H��I��L�Å����C�}���E��itc��x���}��L���x���L��L��H�=�AH��1���H��fDH���`�H�;L�KE1�H��1�H���9��O���@�}u�L�����L��L��H�=BA��1���H����H�;M�H1�E1��H�5A������.������fDL��L��H�=�@1��\�H���\���@��st#E1�H�
�V��1�H�5�@�
��fD�}u�L�����"H��I����H��tML�����L��H�D$��L��L��H�=�@H��1����L�D$H��M9�����L���������L���@�L��L��H�=Q@H��1���H��������SH���#	��H�d�&�V�&����H�߾P�Q��H�:=H�P0��H��H���6��H�
:H���H���H����u��H�3E1�E1�P1ɺH�=�?j@1�j@j@j@jjj���H��8H�3E1�jE1�1ɺjH�=�?j���&1���H�� ���&[�fDH�5��&H������6���@UH�=hH�s���H���+���H����H��jH��H�
��L�
l�A�0���ZY]�ff.�f�AWAVAUATUSH��H�$H��hdH�%(H��$X1�I�����I�\$@H�D$H�������L�t$H���zf�H��L���U�H���=��H�T$PH�t$ M����A���������H�|$ 1�H������&�����D��H��G�1����H�[H��tRL�;M�/M��t
I9m�z���H��L��������g���I�}��H��G�1�H��1���H�[H��u�H�\$1�H������1�H����
��I�L$I�<$1�M�L$(M�D$ PH��A�t$8�5��&1��)�XZH��$XdH+%(uXH��hH��[]A\A]A^A_���D��H��F1�1��������H�|$ ���H�|$H���1�����W������H��H�=a��H�5�FH�������f.�AVAUATU��SL�'H��M�l$ I9�tI�|$H���Y�I�D$H�sH�{��
�������H�5�F1����I���$��H�{HL����1����H�{���H�{ �}��H�{(�t��H�{0���H�{8�b��H�5[�&H�{@1��p��H�{@��H�{H�^��H���6��I9�t![]A\A]A^��H�{H����I�D$ I�|$��I�|$ u�I�|$H��t�H�/I�l$ H���[�H��I�D$[]A\A]A^���fD��H��L�I9x t1����1�H��Ð�5��&1�1�L����1�H��������r���f���SH���C���i�&��u9����H�3E1�E1�P1ɺH�=d;j1�jj�_�H�� �-�&[�H�5%�&H���1���ff.�@��AUI��ATI��UH��H��dH�%(H�D$1��%��H��H���z��H��L��H�$H��������u0H�4$H������H�����H�D$dH+%(u(H��]A\A]�f�L���H��H�q�&H��H�������ff.�@��AWI��1�AVA��AUM��ATE��U��SH��(�D$`H��$��t$1�H�T$x�D$�D$�D$h�L$�D$H�D$pH�D$�`��H�5����H��H���^��H����H��������TA����J��H���m���T$E��A��D��L��H�����I��H���
�L��A����D��1������H��H���H�H��I������L����L������H�����L�����f��H���ZL$f(�����M����L����L��A���I�D��1����=���I��H�����L��I���w��L����L������L���-���
5L���^L$�Z�f(��O��H�����f��f�L��I�ŋD$�T$L��D)��*ȋD$+D$�*��^��^��Z��Z�����L�����L�����L����H���$���H��A����E��1�1���H���H�H��I���}��H���H�5J8H��I���V��H��H����H��H�78I��1�jL���H�8L��UPH�*8PH�38PH�t$81��9�H��0M��tL���8���H�����M��tH��(L��[]A\A]A^A_���DH��([]A\A]A^A_ÐL��E1���L��������D$A�����H�5IAH�������g���H�5i7L���u��H���#���ff.�f���UH��SH������H��&��&��umH��P���H������H��H�����H��IH�
oHH�S0H��HH��HH���H�
�FH���H���H���H��[]�H�5i�&H������ff.�@UH�=�6�s�H���[��H����H��jH��H�
#���L�
|��A�@���ZY]�ff.�f���UH��SH��H���T2dH�%(H�D$1�H��H�t$�+��H��t�D$�EH��t	�$�H�D$dH+%(uH��[]����UH�=*6��H���k�H����H��jH��H�
S�L�
���A�(���ZY]�ff.�f���UH��SH��������&��ud�_���H��H���T��H���L�H��H���A��H��H�
cH���H��H���H�
GH���H���H��[]�f�H�5��&H�������ff.�@UH�=I5���H�����H����H��jH��H�
3���L�
���A�(����ZY]�ff.�f���AUATI��UH��SH��8dH�%(H�D$(1��g�H��H������H�����L��H��I���F���L��H��L���X���H�����H��t0H��H�\$fo$H��H��)D$����H���s��H��H��u�H�D$(dH+%(uH��8[]A\A]����ff.���AWAVI��AUI��ATUH��SH��8�D$dH�%(H�D$(1���H��H�����H�����H�t$H��H�D$���H����H����f��H��A��l$L�d$ H�\$$�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$(dH+%(uH��8[]A\A]A^A_��K�ff.���AWAVI��AUI��ATUH��SH��8�D$dH�%(H�D$(1���H��H�����H���7���H�t$H��H�D$����H����H����f��H��A��l$L�d$ H�\$$�l$�2��L$$�T$ �L$�T$H��E1����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$(dH+%(uH��8[]A\A]A^A_����ff.���AWAVA��AUI��ATUH���5��A�����I��E��tMM��tH��H�����L��H�������uH��L�����H����������E1�]D��A\A]A^A_�H��H������H������H��H��u!��H�����H��H����H��������t���H��H�EH��tH90tH������t����H��H���^��]D��A\L��H��A]1�A^A_��DL��H��A��/��H���g�]D��A\A]A^A_�f.���]D��A\A]A^A_�ff.���UH�������5�&H��&����H��P�A���H��E1�1�H��H��IA�����H�E H�MeH�9H�EHH�;IH�5�3H�E0H�=Y+h��o�H��H���_�A��1�H�L/H�5]/H�=P/�}��H��H���-�1�H�>/H�5R/H�=bPA���K��H��H��XY]���H�5�&H���Q������ff.����ATUSH����H���&���&����H�߾P����H��H�DOH�E0H��MH�EH�~NH�E ���H�3E1�E1�PI��1ɺjH�=�.1�jj����H�� H�3E1�ATE1�1ɺjH�=|.jj��&1����H�� ���&���H��7H�5`.A�H�=\.H����[H��]H��A\����H�5��&H���1������ff.����UH��H����H�}H��t$H��1�E1�E1�U1ɾ�M��H�}����XZH��]���f���SH�GH��H�8H�H��t	���H�CH�xH�@H��t[����D[�ff.���H�G����H�G0��AUA��ATI��UH��SH��H�_dH�%(H�D$1�H�;���H��H�t$H���!�H�}D�$E��L$1�1���D$1�H��H�C(L��H��+�C0�$�C4�0���H�5�)H��H����H������H�D$dH+%(uH��[]A\A]���fD��AVAUI��H��ATI��US���H�XH��H�;����H��1�E1�AT1�L�
����L��I������{XZt;�sL��H�������5�&H��H�K(1�1����[L��]A\A]A^�W���K0�S,�s(H�}D�KD�C4�1�H��L��H��*�2���H�5�(H����딐��AUI��H��ATUH��SH�����H��H�@H�8�5���H���1�UI��L��L�
����E1�1�����L����H�{H��H��[]A\A]���@��H�G`����H��1�H��H�5�������ATI��UH��SH��H�� H�0dH�%(H�D$1�����H�T$H�t$H���P��H��t	�,D$�EH��t�,D$��D$%�\A�$H�D$dH+%(u	H�� []A\���f.���H����@��H�Gx����H��H�0������tH���fDH�������AUI��H�=B*ATUSH��8dH�%(H�D$(1�H�L$H�T$ H�D$H�t$������L�d$ Ld$��H�\$H��I9�v$�H��H����H���]��H�\I9�w�1�H����H�=�����H�T$1�L��H�5��1���H�|$1�����L�����L��A������D��H������H�EH�8H���������8���H�Y)�1�H��1�����H������H�|$���H�|$H��t�}��H�D$(dH+%(u7H��8[]A\A]�f�H�D$1��H�M2H�H1��R��H�|$H��u�������AWAVA��AUI��ATUL�8��H�0�^�H����I�ą�tg��L���u��E��xHD��L����H��L�����L��E1�E1�1�H����H�5{(�\��]L��A\A]A^A_��L���@���H���L��������ff.��������@�������uH�(H�5�?1����D��SH�����u
�����t[�DH��H�5����,1���H�531�����[�}�ff.�f���AUI��ATI��UH��SH���H���,��H���L�(H��L�`H�h����H�����u
�����tH��[]A\A]�@H��H�5���,1��r�H�5�0���H����[]A\A]���@��I��H���H��L��������H��������I��H���H��L��������H����p�����5�&1�1�����ff.�f���H��&H��tH��&��UH�-�&H���p����uH��&]��k��H��H���p�H�Ѯ&]����AUATUH��SH�����H����H�=��&�PI�����H��PHL�e�_���H��H���D��Hc}�&I��$`H�H��t%���I��$`1�H�H��[]A\A]���@L��H���}��1�H�5$/I��$`H���t��I��$hH��tH���I��$`����ATI��UH��S���H��H�����Hcۭ&1�H�L��H�;���H�;[]A\����ff.���UH���s���H��H���X��Hc��&H�H�8H�H��t�J�H�={�&�P��H��]H�@(��ff.�@��AUATUSH��L�/I��`t}��I��H��E��u8��L���~������I��`H��uBH�����H��H��[]A\A]���f�1҉��G��L��H��H���������I��`t�H��1����fDH��H�'$H�5�:1�[]A\A]���f���Hc��&H�<���ff.������A�љA��D�ʉ�HcW�&H�<��ff.���A����D��A�әE���D�҉�Hc�&H�<����D��H��&H��tH�٫&��UH�-ȫ&H���P����uH���&]����H��H���P�H���&]����AUI��ATI��UH��S��H�����H��H���G��Hch�&��t[H�EH�8���H��I�$H�8���UL�
R�1�PA��1�H�
�"A�t$H���S����H��([]A\A]�@H�4H��L��[]A\A]����f.���AVAUI��ATI��UH��SH�� dH�%(H�D$1�����H��H�����Hc��&H�H�+H��t�O��H��H��I���a��H�������u5A�EA�$H�D$dH+%(uTH�� []A\A]A^��H�;L�����H�T$H�t$H���S�f��*D$�A$f��*D$�AE��L��ff.����AVAUI��ATI��UH��SH�� dH�%(H�D$1�����H��H�����Hc��&H�H�+H��t�_��H��H��I���q��H���)����u5A�EA�$H�D$dH+%(uTH�� []A\A]A^��H�;L���%��H�T$H�t$H���c�f��*D$�A$f��*D$�AE��\��ff.��ATH�=� �B��I�����H��L�源jH��L�
���A�8H�
���P���� H��I���`�����&XL��ZA\�f���H�}�&H��tH�q�&��UH�-`�&H�������uH�M�&]��[��H��H����H�1�&]����ATI��UH��S���H��H�����L��1�H�x H�����H�{ []A\�4��@��UH��SH���N���H��H������H�x H��tH�����H�C H�=��&�P��H��H�@(H��[]��f.���H����1�H��H��1�������H��&H��tH���&��UH�-�&H��������uH�զ&]��[��H��H�����H���&]����UH��SH�����H��H�����H�xH���'���H�{ ����H�{(����H�{0����H�{8����H�{P���H�{X��H�=��&�P��H��H�@0H��[]����AUI��ATI��UH���	���H��H���^���H�x`t]H��'A\�1�1�A]�-��D�@xL��1�L��H��H�����H�5����H�E`I��H���ؿ��L�����H��tkH��P���H�5I��H������L��H�58����L��H�5\���L��H�5~����E|1�1��5V�&H��]A\A]�a���H�51'L��聼���ff.�@��AUI��ATI��UH�����H��H���N���H�x`t]H�'A\�1�1�A]���D�@xL��1�L��H��H�����H�5����H�E`I��H���Ⱦ��L�����H��tkH��P�޿��H�5I��H������L��H�5(���L��H�5L���L��H�5n����E|1�1��5J�&H��]A\A]�Q���H�5q&L���q����ff.�@��UH����H��H���H����@|]�ff.�@��UH������H��H������H�xhH�����H��贿��H��P�EH��H�5�]H������@��H���s���1�H��H��1��s�����SH���S���H��t&H��H�H��tH;0tH��������t
H�Ch[�@H��%H�5�01�裸��1�[�ff.�@��SH����H��t&H��H�H��tH;0tH���f����t
H�Cp[�@H�A%H�5:01��C���1�[�ff.�@��AUI��ATI��UH��S��H�����H��H���׽�����vH�
'+��Hc�H�>��H�pXH��L��[]A\A]���f��xx@��@��H��L��[]A\A]����f��pD��t�1��xx@����DH�@01�H��tɀ8@���@H�@81�H��u��H���x���H��L��H��[]A\A]�C��H�������fDH�pH��H��9HD��E���f�H�p ��f.�H�p(��f.�H�p0�f.�H�p8�f.��p@������pD������pHH��L��[]A\A]�;��H�59���@H�pP����H�EH�8謾��H��I�EH�8蝾��UL�
o�1�PA�1�H�
�A�uH���S���H��([]A\A]�f���AUATUH�����H���>H��H�EH��tH;0tH���������Ex����H�}`�/H�}h�v�I�ċEx��tCL�m`�ExH�E`����1�L��L�����L��A��Բ��D��]A\A]��UD��t9H�}p��L��H���m���A�Ņ��H�=A�F��H��t	�8��L������PH��EH����H�5�H�������Ex�N���f��L������\���fDH�H�5:,1�E1�����G���E1�H��!H�5,1����D��]A\A]��H��H�5�+1�E1�蘴������H�5�1�E1�����I���B���H��H���7���L��H�����������1�H�5���I������H��H�����L��H���������f.���ATUH��H���m���H����H��H�EH��tH;0tH�����������Ex��t<L�e`M��tcH�E`�Ex��t/L��1�1����H��L��]A\�԰��@�E|��uYH��]A\ÐL��1��&��H��L��]A\駰���H�oH��H�5~*1�]A\�D���@H�) ������H��H����H��]H��A\������H�m�&H��tH�a�&��UH�-P�&H���`����uH�=�&]����H��H���`��H�!�&]����AVI��AUI��ATI��UH��S���H��H���X���H��H�@ H�8H��t���H�E H�H�x耯��H�E H�x���H�] L�����L�u L��H��8���L�m L��I�F�ؿ���5��&H��1�I�E1�[]A\A]A^骿��f.���UH��SH������H��H��賷��H��H�@ H�x���H�C H�x���H�C H�8H��t����H�C H�H�=�&�P���H��H�@0H��[]��f.���H���c���1�H��H��1������H�G H�8�����H�G H�x�?���ff.�@��H�G H�x�Ͼ��ff.�@��H�]�&H��tH�Q�&��UH�-@�&H���`����uH�-�&]����H��H���`��H��&]����H���&H��t��H������1�H��1�����H�̚&H�������G\���‰���8�tJ��SH��	ЈG\��u
�X��u;[ÐH��H�5V���|���H�55�CX��[�J��f.����CX[��ff.����7�����ATUSH�����L�%��&H�-��&H�CL��H���#�H�C ���L��H��L�%��H�C(��H�C0�k��H�C8�r���L��H��1H��H�C@H�56�u���H�C�xt+L��H�
]&��1�H�5_���f.�L��H��H�5eH���'���H�C�xt%L�>H�
&��1�H�5�<��@���H�CPH�CH[]A\�fD��ATH�(1UH��H��H� ���H��t%I���u��H��L��H��H��]E1�1�A\�K��H��]A\��������t:�����t0H�=��&ATt,�׳���q�&���H�55A\H���k�����L�%�L���Q��H�:�&H��u�L��H���1�迵���ff.�f���ATUH��H��H� �T$H�,����H��t!I�����H�L$L��H��H��A��h��H��]A\���ATUH��H��H� H�T$H�����H��t!I���@��H�L$L��H��H��A����H��]A\�ff.�@��AUI��H��ATUH��SH��H� �<�H��t7L��I���ܳ��H������H��L�CL��[L��H��H��]A\A]���DH��[]A\A]�D���ATUSH����iuM�yuGH�����H��H��tm� 譼��H�{(�`�H��I��H�(���H�uH�{0L��[]A\�����xt�s]�1�1�A\����f��yu�H�����H��H��u�[]A\�D��SH�0��H�w���H��t�H�X[�ff.���SH�0H��H�<��h�H��t�HH�X[�f���AUI��ATI��UH��SH���H��輻��H�{8L�(H��L�`H�hH��[]A\A]���ff.���AVAUATUH��SH��dH�%(H�D$1��(���E\�
I��H�E8�P��t&1�fDH���H���H��H�p�H�E89Xw�����H�U(L)�H�$�B����E1�L�5=�"L��1�1�蟲��H�U(A��D9bvqH�
D��H���C�t�L�I�I�	��i����xu��t
H�CH9Ct�L��H�KA�L��H��A���=���KH�CH�U(H�CD9bw��H�EH��A�L��H��H�H�P���H�D$dH+%(uLH��[]A\A]A^�@�t�C9C�5���L��H�KA�L��H������C�KH�U(�C�
���� ����H���S����H���f���AWAVAUATUSH��XL�HH�t$H�T$dH�%(H�D$H1�H�G@H�H�D$(H���I��H�l$0H�D$(L� A�$����I�\$E1���D��I�I�MH�	H��L�YA�;�6A���i��A�{��D��H��A��
H�L$ D�D$����t$H���s��H�L$ L�YH�qL��L�D$H��H�D$L���H���Y��E94$v5f�A�FA�VH�D$@)D$0�f���D���L�<A��E94$w�H�D$(H�@H�D$(H�������H�D$HdH+%(��H��X[]A\A]A^A_�@��xu{A�{utL��(H��A��H�L$ L�D$�E��H�t$H���x���H�L$ L�Y� ���f.��@H��H�L$ �T$�
��H�L$ D�t$L�Y������su[A�{uT�@H��H�L$ �T$�����T$H��L�L��L�D$�ƺ��H�|$�L���H�L$ E�tL�Y���f�A���~��������AWI��1�AVAUATUSH��H�T$H�4$���H�5�
H��I������I�W�B����1��]fDI�T$1�H��L��H�5T���M��tH�5F
L�����H�5O
L�����I9l$tH���գ��I�W��9ZvYH��ؾ"L�$�I�l$H��莫��H��tH�����H��I�t$I�0�P���I�Ņ��e���H�5:L���&���Q����L��H�5�	���1�L�����H��H�����L�L$H�<$1�H��H��H��E1�[]A\A]A^A_�ŵ��D��AVE1�1�AUI�պATI��UH��SH��H�� dH�%(H�D$1�H�\$H�4$H�5"	I���D$H�D$�a�����uH�T$dH+%(ucH�� []A\A]A^�H�59��H��L�����H�t$H��tL���L���1���E1�1�I�ٺH�5�H������������e���D��H�E�&H��tH�9�&��UH�-(�&H���p�����uH��&]����H��H���p��H���&]����AWI��AVI��AUM��ATI��USH��8L�L$xH�T$pH�L$(L��$�H��$�L�D$ L�L$L�T$H�$H�T$�F���L��H���+����PH������L��H�H����L��H�E���H�|$(H�E �ֱ��H�|$ H�E(����L��H�E0輱��H�|$H�E8���H�5��&1�H�E@H���)��H�$H�T$1�L���f��L�L$1�H��H�EHH�5���L�ML�����H�{H��H�E�u���H�CH��蹪��H�{ tH��8[]A\A]A^A_�H�{H��t�H�/H�k H������H��H�CH��8[]A\A]A^A_�^��ff.���AUATI��USH��H��dH�%(H�D$1�I��H�$���1�L���蠻��H��tcH����H��H��踨��1�M��E1�H��1�H�����H�4$H�C(H��tL��讦��H�����H�D$dH+%(uKH��[]A\A]�H�4$H��tL���w�����D���1�H���1�虴��H�$H��H��t���薼��fD��U�&���1�H��1�躟��H������H��]H�����fD��SH��H�H��t1�H�5����A��H�CH�{ H��t
����H�{(H��t
���H�C([�ff.���UH�����H��H���x���H�����H�=�&�P����H��]H�@0��D��U��SH��H���L���H��t7H��H�H��tH;0tH���O����tH�{ H��t2H����[]�U��DH�	H��H�5�1�[]����DH�������H��&H��tH�	�&��UH�-��&H���P�����uH��&]��K���H��H���P��H�Ɋ&]����AVI��AUATUH��SH��dH�%(H�D$1��u���H��H���:���H�$H����H�XH�{�<���H�{ I���@��H�{ ���Ů��H�{ A��1҉�1��c���H�{H�5qI��耨��H��H����H�ejL��1�UL�
{H�KL��PH�XP1�L�D$ �O��H�t$ H�� H��tQL���)���H���ќ��M��tL�����M��tL���ל��H�D$dH+%(uHH��[]A\A]A^�f.��L��胶��먐L��H�
��x1�H�5�����z���H�{H�5�蚧��H������f���AUI��ATI��UH��SH������L��H���ɤ��H�X�`��L��H��赤��1�H���K��H�����H���	���H�{ H�C H��t�3���H�{H�CH��t�ݛ��H�{H�CH��tH��[]A\A]�-��DH��[]A\A]�ff.�f���AWAVAUE��ATM��UH��SH��L�t$PL�|$X�t$�T$�L$�*���H���!H��H�EH��tH;0tH���������M��t"�&���H��I�$H��tH90tL���ݾ����uH����DH�]H�{t]M��tH�#���L��H��1�A��H��H�
:���A�H�D$PH��1�[]A\A]A^A_�J���f.�H��[]A\A]A^A_ÐL��1�L��H������H�5��I��H���ޡ��L������H���U1�H��L���P���L������D�k4H�C�D$�C(�D$�C,�D$�C0�&�����tzH�}D�C4E1ɋK0�S,�s(���5��&1�H��1�H�K(�c���1�H��L��H������?��H�5���H��H���
��H���$���H��H��[]A\A]A^A_龙��fDH�;�@��H�;I���%��L��H��������H��H������H���b���L��H��E1�H�E1�H����H�CH��H�5�[]A\A]A^A_�W����H�iH��H�5~1�[]A\A]A^A_�ݛ��DH�5L��虝�����@��AWAVM��AUI��ATI��UH��S��H�����H����H��H�EH��tH;0tH���y�������M��t"藽��H��I�$H��tH90tL���N�����uH���efDL�}I�tMM��t3蓛��H��L��L��A��H�
H�
H���H��PA�1��Ƕ��XZH��[]A\A]A^A_�fDL��1�L��H���`��H�5	���I��H���^���L��薦��H����1�H��L���и��L��E1�腗����I�GA��趵����t2L��D��H���T���5��&H��1�H��I�O(1�[]A\A]A^A_��I�?���I�?I�����L��H���¿���}��H��H����H���:���E�gL��H��A�GH��E1�E1�[H����H�5��]A\A]A^A_�1����H�I	H��H�5N1�[]A\A]A^A_齙��DH�5��L���y������@��AUI��ATI��USH��H�����H����H��H�H��tH;0tH���c�������L��1�L��H������H�5����I��H���ٝ��L������H����1�H��L���K����F�����tH�{H��L��[]A\A]��@H�[H�;���H�;I�����L��H���f����!��H��H��薞��H���޴��H��L��H��[E1�]E1�A\H���H�5R�A]���DH��H��H�5�1�[]A\A]�q����H�5L���1����4���ff.����AWM��AVI��AUA��ATA��USH��H���*���H����H��H�H��tH;0tH���	�������1�L��L��H�����H�5����H��H������H��跣��H����H�{E1�D��A�H��D��D�g(D�o,H�G0�k��H��议��H��tH��H��[]A\A]A^A_鳔��H��[]A\A]A^A_ÐH��H��H�5�1�[]A\A]A^A_�=���DH�5+�H������V���@��AVAUI��ATI��UH��SH��H�����H����H��H�H��tH;0tH���޷������H��t%�<���I��H�EH��tL90t;L��H��谷����u,H���H�5�1�藖��1�H��[]A\A]A^��H�5����H�������u%H�.H�5�1��D$�T����D$�fDL��H��H�[�	���L��H�������t��M��t�H��(I�$H��[]A\A]A^�H�qH�5J1���H��1�[]A\A]A^�@��AWAVM��AUM��ATI��UH��SH��H��L�H�L$��H��I�$H��tH;0tL��蘶�����H��t%���I��H�EH��tL9 t5L��H���j�����u&H�u�H�5(1��Q���1�H��[]A\A]A^A_�H�5����H���q����uH�.H�5�
1�����1���@L��H���՚��H�t$PH�����H��t�H��t
�AG<�H�\$I�@I�G@H��t6H�;M��tI�WHI�UM���f����AGP�AH��[]A\A]A^A_�H��t�H�D$�ɑ��H�D$�f�H��H�5B
1��k���H��1�[]A\A]A^A_�f.���AVAUI��ATI��UH��SH��H���/�H����H��H�H��tH;0tH����������H��t%�l���I��H�EH��tL90t;L��H�������u,H��H�5	1��Ǔ��1�H��[]A\A]A^��H�5I���H��������u%H��H�5�1��D$脓���D$�fDL��H��H�[�9���L��H��������t��M��t�H��(I�$H��[]A\A]A^�H��H�5z1��#���H��1�[]A\A]A^�@��AVAUI��ATI��UH��SH��H�����H����H��H�H��tH;0tH���γ������H��t%�,���I��H�EH��tL90t;L��H��蠳����u,H���H�5^1�臒��1�H��[]A\A]A^��H�5i!H�������u%H��H�51��D$�D����D$�fDL��H��H�[���L��H�������t��M��t�H��(I�$H��[]A\A]A^�H�aH�5�1����H��1�[]A\A]A^�@��AVAUI��ATI��UH��SH��H����H���&H��H�H��tH;0tH��莲�����H��t%���I��H�EH��tL90t;L��H���`�����u,H�k�H�5�1��G���1�H��[]A\A]A^��M����H�5���H���X�����u$H�=H�5v1��D$����D$�DL��H��赖��L��H���J�����t�H�[H�{ �)�������H�{ 踣���PA�T$�PA�$�PA�T$�A�D$H���[]A\A]A^�@H��H�5�1��k���H��1�[]A\A]A^�@H�z�H�5�1��C���1����@L�H�
��1�H�5���g������AUATUH���~H��I���c���I��H�EH��tL9 tL��H���װ����tSH�5����H��������u H�)H�5�1�諏��]1�A\A]�L��H���e���]L��A\H��A]�u���DH���H�5�1��k���]1�A\A]���H���S�1�H��H��1��Ì����H�=y&H��tH�1y&��UH�- y&H��萤����uH�
y&]�軹��H��H��萵��H��x&]����AWA��AVI��AUA��ATI��USH���}���L��H��肔��D��L��H���Գ��H�S0L�C(L)�H��H�DL9���H�{ D��H�T$H�<$裳��H�<$H�T$H��H��H)�H�D
H)�H�$H��}���H�$HK H��H��L���ן��H�C Hk0D��Dk8L��D��L��H�k0�(�"���H��D��[]A\A]A^A_�M��uWH�C(H����H�C( H�� ��A�@L�C(L9�w��H�{ L���t���H�S0H�C H��� ���M�I����vǸ��H�C(��A���H)�H9�s�L��L��L�$H)�L��x���L��L)�H��H�����A���A���@A��A� �u���D��UH��SH��H������H��H�����H��tH�P0H�H�@ H�^H��HD�H��[]�ff.���UH�����H��H��蘒��]�@8���AWAVAUI��ATA��U��SH���a���L��H���f���H�Ë@89��F�A�,)�9�DB�E��uH��D��[]A\A]A^A_�fDL�{ ��L��肱��A�4,L��I���s���L��M)�H��H��H�C0L)�H�PH�L$H)��O���H�L$D)c8��D��L��I)�Ls0�b������UH��SH�����H��H��賑��H�x H��t'H��貒��H�C H�C(H�C0�C8H�=�u&�P��H��H�@0H��[]����H���C���1�H��H��1�����ATUH��SH������H���H��H�EH��tH;0tH���;�������H�����ō��H��H�H��tH90tH���
�����uH�����DH;]h���Y���H��I��H������L������E1�E1�H��H�c��H�5��H������H��蜇��H�}hH��u4H�]hL�%Pu&H��P�[���[L��]H��A\���@H�}hH��tOH��1�L�
��E1�U1ɾ��H�}h艇��XZ�DH�)�[H�5�]1�A\�'����[]A\�ff.���ATUH��SH���m��H����H��H�EH��tH;0tH���۪������H�����e���H��H�H��tH90tH��譪����u	H����wH;]p������H��I��H��賊��L��軆��H���c���H�}pH��u3H�]pL�%t&H��P�"���[L��]H��A\鳣��H�}pH��t'�r�����H��[H�5�]1�A\�����[]A\�ff.���AVAUI��ATI��UH��S���T��H��H��詎��I�ƍC���
wH�C�Hc�H�>��DH�EH�8�ܐ��H��I�EH�8�͐��UL�
��1�PA��1�H�
��A�uH����S�-���H�� []A\A]A^�I�~P�w���L������H�5��I�FP[H��]A\A]A^����@I�~X�G���L���ߟ��H�5��I�FX��f�L���(���[L��]H��A\A]A^���DL������[L��]H��A\A]A^����DI�~���L������H�5��I�F�k����I�~ 迄��L���W���H�5��I�F �C����I�~(藄��L���/���H�5��I�F(�����I�~0�o���L������I�F0H����H�5��H�����H�5�q&[H��]A\A]A^鄡��@I�~8�'���L��连��I�F8H��t~H�5{�H��跧��H�5�q&�fDL���P���H�5b�A�F@�|���L���8���H�5�H��A�FD�u���H�5>q&�u���f�H�=��4���I�F0�A���H�=�����I�F8�m�����ATI��UH�����L��H������H�ŋ@|��upH�}`t1H�����H�}`t"L�_�H�
��&1�H�5F��Q����H��1����H��1�����H�=�p&�P�+���L��H�@(H��]A\��蓅��H��H��舋��H���p����s���ff.���H��o&H��tH��o&��UH�-po&H������uH�]o&]��۰��H��H����H�Ao&]����H�o&H��tH�o&��UH�-o&H��萚����uH��n&]��k���H��H��萫��H��n&]����H��n&H��tH��n&��UH�-�n&H���0�����uH�un&]����H��H���0���H�Yn&]����UH��SH�����H��H���3���H��H�@0H�x�s���H�C0H�x�f���H�=n&�P腴��H��H�@0H��[]��fD��AUI��ATI��UH��S��H���"���H��H���lj����tj������t|H�EH�8����H��I�$H�8���UL�
˿1�PA�w1�H�
�A�t$H���S�X���H��([]A\A]�DH�@0H�pH��L��[]A\A]鮖��fDH�@0L��pH��[]A\A]�o����H�@0H�p�ff.���AWAVAUATI��UH��H��SH��XdH�%(H��$H1��*����Ã���v	��������H��H�E0H�8裈��H���[���I��H���L���G���H���O���H��I���4���H��I���ـ��L���1���L��H���v���H������H������H�t$H�T$L��H�D$�_���H�D$L��DŽ$�H��$H��$HDŽ$�;���L����H��$�)���L��L��$��������$ �I���1�M��H��DŽ$8��L���ЋT$�$ ���$(1҉�$$D$��$,H�H��$0�������H�D$L��H��$�HDŽ$�H��$�H��$H��$�H��$ H��$�H��$(H��$����L��L��$�DŽ$���$��	���1�1�M����H��L��DŽ$���$��R������q1�1�L��M��DŽ$�H���,���L�����H��$HdH+%(�HH��X[]A\A]A^A_�H�D$L��H�\$ H�l$@H�D$PH�D$HH��$H�D$XH��$ H�D$`H��$(H�D$h����L���D$x�D$p�D$ �`���I��1�1�H��L��D$t�z����D$ I��1�1�H��L���`�������H��$HdH+%(�|H��XH�+�H�5��1�[]A\A]A^A_���H��$HdH+%(u@H��X�1�1�[H���]A\A]A^A_�?����DŽ$�M���V���蠙����H�
i&H��tH�i&��UH�-�h&H��谔����uH��h&]�����H��H��谥��H��h&]����AVAUI��ATI��UH��S�����H��H��詄����t\H�EH�8���H��I�$H�8���UL�
��1�PA�e1�H�
�A�t$H����S�H���H�� []A\A]A^�L��I���ݮ��H��tI�V��B[]A\A]A^�fDI�F�@�[]A\A]A^�ff.����AUI��ATI��UH��S��H�����H��H���׃����tZH�EH�8�&���H��I�$H�8����UL�
�1�PA�x1�H�
=�A�t$H�!��S�v���H��([]A\A]�H�pH��L��[]H��A\A]鮌��ff.���UH��SH������H��H���3���H�PH�:H�H��tH���z��H�SH�zH�BH��t讧��H�=�f&�P�m���H��H�@0H��[]��ff.���H�����1�H��H��1��cz����USH��H���.J��H��tyH��H�H��tH;0tH��豝����t]H�{01�����H��H��u�4�H�[H��t#H�;�ߘ����u�H�3H��� ���H�[H��H��u�H��H��[]�y��fDH���H�5��1��;|��H��1�[]�f���UH���I��H��H�����H�-qc&�PH���ԁ��H��]H���h������UH���CI��H��H��訁��H�-9c&�PH��蔁��H��]H���(������UH���I��H��H���h��������u�]������H�5�H������]�D��UH���H��H��H�����������u�]���{��H�5��H���L���]�D��SH���cH��H��t&H��H�H��tH;0tH�������t
H�CH[�@H�.�H�5��1���z��H�l�[�f.���SH���H��H��t&H��H�H��tH;0tH��膛����t
H�CP[�@H�ηH�5
�1��cz��1�[�ff.�@��ATI��UH��H���G��H��tuH��H�EH��tH;0tH��������tXH�}PL���l�����uH��]A\�H�}P�w��L��蟇��H��PH�EP���H��H�5��]H��A\阚���H��H��H�5.�1�]A\�y��@��ATUH��SH����F��H����H��H�H��tH;0tH���l������tH�{hH���t[H�5n�]1�A\�Dy��@諝��H�khH��H�C �bH��H�Cp��v��H�C0H��H���pw��H��H�C(�4���H�C8�ˢ��H��I����y��L��H���~��H�C��������H��H�=�~��L�%(��΁��H��趩��H���~���H�{E1�E1�H��H�j���H�5N��ލ��H�{E1�E1�H��H�����H�5<�辍��1�H�ڿH�5�����y��H�{E1�E1�H��H����H�5��舍��1�H�ڿH�5����y���m��L��H���H�5��H������O��L��H���H�5��H������1��L��H���H�5t�H������H�{E1�E1�H��H��^��H�5�����H�{0E1�E1�H��H�$��H�5���،��H�{@tH�{0E1�H��H��}��H�5���D����ϛ��H������H��E1�E1�H�5��H� }��H��H��腌��H�{茥��H�{H��H���[]A\�e�DH��訠��H���p���H�C@�#����H�F����@��ATI��UH���͠��H��a&��a&����L��P���H����L��H�����H�
�H��
H�M H�
�H�5H�MH�
}���H�U(H�b���H���H�
�H���H�H���H���H����<>��H���H�5��A�H�=��H���i���H��H��]H��A\�”��f�H�5�`&L���!w���$���ff.����ATI����=��M��t=H��I�$H��tH;0tL��褖����t 蛴��L��1�A\H��H�58�1��s��H���H�5��1��ku��1�A\�fD��ATI���b=��M��t=H��I�$H��tH;0tL���4�����t �K�L��1�A\H��H�5��1��r��H�9�H�5��1��t��1�A\�fD��AT1�1�L�
��UE1�H��I��SH��V��e���H�EL��H�x蕋��L�`H���<��H��L���nz��H���6���H�CH���:���H�KXH��5,^&Z1�1�[]A\����ff.���ATUH��SH��H��dH�%(H�D$1��9���H��H���y��I��Hc_&I��l���H�=_&H���]���H��H���H��H�t$H�����K�\�@��C�\CI�<$�,�f���Z$�X��D,��,�f���ZL$�X��,��a>��H�D$dH+%(u	H��[]A\��Í����UH���s���H��H���8y��I��HcV^&I�<�?��蠜��H�=I^&H��董��H��]H�����@��UH���#���H��H����x��I��Hc^&I�<��?���P���H�=�]&H���A���H��]H�����@��AWI��H��AVAUI��ATI��USH��Hc�]&H��w��I���[w��H�;H���px��H����x��H��tH��H���(r��L9�tH��[]A\A]A^A_�@軛��L��I��谤��L��H���%x��I���w��L��H���x��L��H��跆��L���o��E1�L��L��H�CA�H�Յ��H�5���Y���1�L��H�C�k����L���>�ɋ��1�1�H��I��H���g���L��诗��H������H�sH���7���H�CH��L��[]A\A]A^A_鼇��ff.����ATI��SH��Hcs\&H��CA��H���;���H�{uH�{t
H��[A\�DE1�E1�L��H��H�]���H�5��聆��H�C��ff.���SHc@[&H�H�_��@��H�[���AWM��AVA��AUM��ATI��UH��SH���T$dH�%(H��$�1����H���7H��H�EH��tH;0tH��螑�����M��t"輒��H��I�$H��tH90t3L���s�����u'H��$�dH+%(��H����@H�]H�;����H���ā��H�{�iH���`L��1�L��H��蠝��H�5	���I��H���t��L����{��H���5L����l��L�eD�s8H�CH�\$`I�<$蛤��H���S���I������L��I������L��H���uu��H�T$<H�t$8H��I���P���H��L���u���A�L$8���8foT$`AT$(���L��H���,u��1�H���Ґ��I�D$ H���T菏��I�D$�D$��5QY&H��H��1�1��|��H��L��1�H����薜��H�5_��H��H���d���H���,l��H��$�dH+%(�DH�Ĩ[]A\A]A^A_�M��t��&o��H��L��H��A��H���H�
����1�PA�1��[���^_��H��$�dH+%(��H���H�ĨH�5d�1�[]A\A]A^A_�Cn��H��H��L���s�����DL����������Ao\$(I�D$ )\$pH�D$��=��H���Ţ��H��I���{��H��I���o���I��H�������H�|$p�{��1�H�t$XL��H�D$�U����,L$\H�|$�,t$X�ʉL$�t$蕚������H�T$DH�t$@L���kp��L������L��A������D�|$(A���ljD$,A��Hc��]}��D����L��H��H�D$ �����L$,�T$(E��H�|$ 1�讌��H�|$H�T$PH�t$HI���7����g�f.D$HD�T$,zu
f.D$PztW�D$H��$�L��D��$���$��D$��$��D$(��$���s��L����3s��f��L���Z�f(��On��H�|$�q���T$�L$L��+T$D+L$@f�I��+T$t+L$pf��H���*��*����L������L�����L��跈��H�|$�
���H�|$ �Ci�����fDL���0k��I�|$ f���Z�f(��m������D�[l��H��L��E1�A��H���1�H��PH�5���1�蓇��XZ����@H�5��L���ym�����@H�|$�v����b����̅��ff.����UH��SH���>;��H���6���H��1ɾUH��E1�E1�1���w��H�]Y^H�{H��tAH��1ҾE1�UL�
�1��w��H�]XZH�{H�sH��t�x��H�CH�]H�;H�H��t	�&h��H�]H�{H�CH��tH��[]�F���fDH��[]�f�ATI��UH��SHc�U&H��g:��H���_���H�;thH�sH����H���4~��H�;1��J4��H�;�g��H��E1�1�UH�;L�
�1Ҿ��v��H�,$H�;1�L�
����E1�1ɾ��v��XZL�#M��tQL���g��H�;H����3��H�;E1�E1�H��H�cH�5���7��H�;E1�E1�H��H�D���H�5���������H��H���o��[]H��A\�|���@H�CH�����������AVAUI��ATI��UH��S���t���H��H���9o����t\H�EH�8�q��H��I�$H�8�yq��UL�
K�1�PA��1�H�
��A�t$H����S��p��H�� []A\A]A^�L��I���w��[L��]H��A\A]A^�:���f.���ATUH��H���ͧ��H��H���n��H�=�S&�PI�����H��P(H��L��1�]A\������H��1�����f���AUATUH��SH��HdH�%(H�D$81��z�H��H���n��1�H�T$0H��H��H�5��1�H�D$�k���H�|$0�w�eH������H�|$0H����m��H��譈��H��L�c0H���m��H�|$0I�$� e��H�k0H�}�sm1�H�EH�C0H�8H�P�s��t��H��H�C0H�8�ym��H���1��H���9g��I���Al��H��H�C0H�8�Rm��H���q��H��H���e��H�5?�H���z��H��I�����E1�1�L��I��������H��L��H�D$PH�D$ PH�D$0PH�D$$PH�D$HPj�j��H��0H��A���i���D	�t4H�|$H��t�Յ��H�D$8dH+%(uTH��H[]A\A]�f.�H�|$(u�H�|$ u�H�|$H�C0H��P��H�i�H�5��1��f����̀��ff.����H�FH�8t2SH�����H���qk��H�{[H�HH�PH�?H�pL�@�t{@�ff.�@��USH��H��H�oH�}H��tH��[]�{uH�
!���1�1��Hs��H�EH�k�;uH��E1�E1�H�EH�CH�sH�5��H�8�{��H�CH��E1�E1�H��H�5��H�8��z��H�CH�8H��[]�tff.�@��AVAUATUH��SH���95��H���1���L�kI��I�}H9�t%M�uH��tL���0s��I�mH��tL��H���b��L���Ď��H��tH�����A�H��L��H����H�5���ؗ��A�H��H�X���H�5�L��蹗��E1�H��H��H�I���H�5��蝗��H�CH�8t;H���+���H���i��H�{[H�H]H�PA\H�?A]H�pL�@A^�y�[]A\A]A^�ff.�@��ATUH��SH���aH�{H���j��H���k��t[]A\�H�f��f��f����H�@�P
�*��P	�@�*��*��^��^��^��ܔ��H��I���j��L��H���&j��[L��]A\��k��f.����ff.����ff.����*L�Bu;UH��M��tH�J H�rH�z1�A��H�}�`��H�}�`��H��]�~`��fD�����ff.����ff.���UH��SH��H��H�BH�x��y��H��t6H�HH��t�5�L&1�H��1��p��H�CH��H�xH��[]���DH��H�M�H�5��1�[]�b��ff.�f���H�5]������AWI��H��AVAUI��ATI��UH��dH�%(H�D$1�I��H�$��L��L��H��H����H�<$t�b��H�<$�����w������L��膅��1�L��L��L���V���H�<$I��t�wb��H�<$����w�����L���G���M��tb�
���L��H���g��1�1�H���Fq��L�����x��L����^��H��tH����^��H�D$dH+%(uRH��]A\A]A^A_�fD1�L���x����@H�4$L����b��M��t�L���^���DH�4$L���b����}{��ff.�f���AUATUH����H��I��I����H��H�EH��tH90tH�������tc1�H��L��L���s���H�5����H��H���qe��H���l��H��tTH�5���H���%���H��tH��]A\A]��]��]A\A]�f.�]H�2�A\1�H�5�A]�v`��fDH�5��H���1b���ff.�@����1��@ATA��1��D��������x�t1�A\��D��1�H���1���g��1�A\�ff.���U�w(H�����}H�}H��t'H�u ���H�}H�EH��t�\��H�E H�}H�EH��t��\��H�}0H�E0H��t�\��H�}H�EH��t�\��H��]�\��f�H�?��c���E(�o���ff.����UH��H��SH���~��H��H���`o����u$H�{0tH���}n��H��H��[]�?\���H��[]��AUI��ATI��UH��SH��dH�%(H�D$1�H�$�,���L��H���d��I����b��H��H���nd��H��L��H���`���L��H���~��H�<$H����H�x0t(L�9�H�
���W1�H�56�蟋���H�P0H�53�1�H���Sx���Ct}H�<$H��t��`��H��tH���B[��H�D$dH+%(uqH��[]A\A]��H�HH�U�1��1��e��H�4$L��H�$�3_��L���Z��H�<$H��u��L����t��H�<$H���r����r�����w��ff.����ATM��USH��0dH�%(H�D$(1��̄��H�|$PH���c��H��H���m����t H�D$(dH+%(��H��0[]A\�H���|��H�x0H��t�L��H�L$ H�T$1�H�5��L�L$L�D$�	w��H�s0H�|$ �;�����u�H�|$H�5���&�����u"�H����s��H����Y���h����H�\$�]��H�
'�1�H���I��1��������v���AWA��L��AVI��1�AUATUSH��L��H��8H�t$H��dH�%(H�D$(1��ĉ��1�H�T$ �H�D$ I��H�D$��Z��H����H��荀��H�t$���1�����������8�8�v���L��I���{i��H��I�E�X��E�}I�EE����H�����L��L����y��H��t$H���X��1�L��H��I�EH�5<����ǁ��I�E H�=b��]��ATM��H��H�
h���H�n�QH�5��H�
*�Sj�jPL�L$8�r���H�|$HH��0�DX��H���\X��H�D$(dH+%(�H��8[]A\A]A^A_�f�H�i���jE1�H��ATL���H�
�PH���H�5�j�;q��H�� A�E(����fD��A�ʼn��,p��D��H���Q}��A���Z��I��D��L���H�
7�1�����H�|$�W��H���W��M���9���L���W���,����H�t$ L���[��H�|$�IW������Z��H�
��L���1�蓋����<t��ff.����SH�����H�LE&�>E&��uJH��H�C0H�p���H��H��WH���H��cH���H�VaH�� [�DH�5�D&H���Z������UH��H�=yE&�P觉��H��]H�@0��ff.�f���UH��H�=�D&�P�w���H��]H�@0��ff.�f���ATUH��S����H��E&��E&�����6���H��H��I���(���H��PH������H�=�L��A��@H��H��.H��H��H���H�Z/H���H�lH���H�0H�E(H��-H�E H��,H�E迈��[H��H��D&]�A\H��D&�@i��H�5�D&H���qY���A���ff.����AVAUATI��UH��SH��`dH�%(H�D$X1�L�l$ H�\$0�;t��H��H���]��H���X���H��L��I���}��L��L��L���,���H�T$H�t$L���zm��H�T$H�t$L����b��H��H��H�l$�o���(fD�\$�T$�L$�D$H�|$�n���H��H��胇����u�H�D$XdH+%(u
H��`[]A\A]A^��mq��ff.�f�UH�=���So��H���[s��H����H��jH��H�
��L�
,�A�0�aW��ZY]�ff.�f�H��H�=D��o��H��L�
�}A�(jH�ƺ��PH�
	q�W��H���ff.�@H��H�=��n��H��L�
��A� jH�ƺ��PH�
���V��H���ff.�@H��H�=���`n��H��L�
�PA�HjH�ƺ��PH�
��tV��H���ff.�@H��H�=}��n��H��L�
�A�hjH�ƺ��PH�
	�$V��H���ff.�@��UH��SH���~��H��A&��A&�����$d��H��H��詅��H��PH��虅��H��H�?1H���H��1H���H�s-H���H��0H���H��1H�E(H��2H�E0H��/H�E ��z��H�5��H�=��A��H��H���r��H��H�*A&H�+A&H��[]�e���H�5A&H����U���#���ff.��ATH�=G��l��I���*c��H��L��jH��L�
2A�(H�
�����T��� H��I����p����@&XL��ZA\�f���SHc�@&1�1�H�
	:&H�1��Na��H�C[����SHc�?&H�
�	H��9&H�5�9&H�H�_H�=�9&�a��H�[��AWf�I��AVAUATUS�H��xLc-@&dH�%(H�D$h1�L�d$@)D$ L�t$I�L��H�l$0I�EI�UI�uH�T$H�D$�:d��f.�1�L��L���k����t?H�D$H��H�8������tfoT$01�)T$ ��@H�t$ H��1�H���X���@f�f��I���*D$ L���*L$$�\��f�f��L���*D$(�*L$,�}��H�t$L���u����t;�^a��L��H���cX��H���Q��H�D$hdH+%(u4H��x[]A\A]A^A_�@H�-�>&L���P�$X��H��H���l����l��f���H�����@���Wi�����SH���|��H��=&��=&��uRH�߾P�E���H��H�3E1�H�E1�1�H�P0H�=��1�jjj�b��H�� �@=&[�fDH�55=&H����R���ff.�@��AUATUSH��H���z{��H��=&�}=&����H�߾P訁��H���H�P0��j��H�3E1�E1�P1ɺH�=�j1�jj�sa��H�� ��<&�j��H�3E1�E1�P1ɺH�=��j1�jj�?a��H�� ��<&�pj��H�3E1�E1�P1ɺH�=��j1�jj�a��H�� ��<&�\���I���T���I���Le��H���$j��H��H�3E1�AUE1�1ɺATH�=v�UP1�jjj�`��H��@�8<&��i��H�3E1�E1�P1ɺH�=�j1�jj�`��H�� �<&�i��H�3E1�E1�P1ɺH�=��j1�jj�N`��H�� H�3E1�jE1�1ɺjH�=��jjjj��;&1��`��H��(H�3E1�jE1�1ɺjH�=��j��;&1���_��H�� �~;&�i��H�3E1�E1�P1ɺH�=��j1�jj�_��H�� �N;&�	���H���Ah��jH�3E1�UE1�1ɺPH�=m�1�jjj�y_��H��(H�3E1�jE1�1ɺjH�=S�j��:&1��K_��H�� ��:&蜁��H����g��UH�3E1�jE1�1ɺPH�=$�1�jjj�_��H��0��:&�]��H�3E1�1�PL��4&�1�jH�=��jj��^��H��H�3E1�jE1�1ɺjH�=��j�]:&1��^��H�� �P:&�7g��H������UH�3E1�jE1�1ɺPH�=��1�j�k^��H�� �:&�f��H���h��UH�3E1�jE1�1ɺPH�=��1�j�0^����9&H��([]A\A]��H�5�9&H���N���O���@��USH��H���w��H�G9&�99&����H�߾P��}��H���4X��H��H���}��H�B7H�
[8H�U0H��;H���H�
�H���H�H���H�����q��H����j��jH�3E1�UE1�1�1�j@H�=��P1�j@jjj�@]��H��@H�3E1�j@E1�1�1�jH�=��jj�j8&1��]���a8&H��([]�fDH�5Q8&H����M������ff.��ATH�=K���d��I���:W��H��L���jH��L�
r���A� H�
������L���H��I����h����7&XL��ZA\�f�AWAVAUATUSH��H��L�g�R��H������1��1^��L�k8L�{@L�sH���V��L��H���Q��M��H��L��H��1�A��H�K�5k7&L��1�1��[Y��I�D$H�sH�8�o��H��H��[]A\A]A^A_�DN��@��AVI��AUI��ATUH���(SL���~[��H���I���MH��L��I�D$�@H��M�t$L��L��I�\$ H��[H�]I�D$A\A]A^��J��@��AVI���(AUI��ATUH��SL���[��L���I����G��H��I�D$��G��M�l$I�D$I�\$ �\i��H��H����{��H��tLH����I��I��H��tdH��L��H��1�jL���1�H�=:&�%_��XZ[]A\A]A^�f.�L���H�
���T1�H�5E��w���L�Z�H�
���V1�H�5��ww�����UH��H�?�pR��H�}�GG��H�}�>G��H�}�5G��H�} �G��H�}(�c��H�}HH�EHH��t�]z��H�}P�M��H��]��F��ff.���UH���G@H�0�Q��H�}0H�E0H��t��F���M^��1�H��H�Q?H�E0H���v��H�5����H��H����M��H���+U��H��t&H�5AH���s��H��tH���jF��1�]�fDH�5=�H����J����ff.�@��SH��dH�%(H�D$1��D$��uH�D$dH+%(u,H��[�@1�H�T$L���Qi����t�D$t������b��f���UH��H�H�EH��t�Re��H�}H�5�.&�Rb��H��]�yE��f���AW1�AVAUATUSH��H�T$H�T$8dH�%(H��$�1�H�D$8H�D$0��P��H�D$�kH��H�|$8����]���D$���6L�|$I�GH�@H�D$ H�D$8H����H�XH�l$0�M��H�
��1�H��I�؉�1��xw��H�|$8�>J��I�_8M�w@L�l$0M�g�8R��I�H���<M��M��L��1�H��L���H�D$ I�wH�8�[k��H���j��H��$�dH+%(��H�Ę[]A\A]A^A_�f.�H��$H�5�~H��H�D$(�4[��H�D$H���MI��H�\$PL�l$@L�5��	M�M��txI�/H���J��I��H��t�H��H�l$H�w]��H��H��H�$�W���fDH�|$HL����y������L��H��H���=^����u�H�<$�@c��L���n��M�M��u�H�5h,&H�|$�.`��H�|$(�I��H���Z��H�58�H��H�D$L�` L���i����t�L$����H�D$�@0��H�\$1�H��L��H�{�r��H���TH��H�KH�{1�L�K �5`1&H�CH�C0P1��s(L�C�JS��XZH���\���H����B���O���f�1�L���g��H���I��H�T$@H�|$(H�5R}H��1��Ct���D$���fDH�=��L��$���F��L��H���qY��L�|$H��L��H�5s�1�I�W ��s��L��I�_8M�w@�H��M�oI���O��I�H���J��1�M��L��H��L���H�D$ I�wH�8��h��H�|$0�!h��H���u���H����A���h���H�|$8�NG���V���H�5�*&1��{^��H�|$(�H��H����X��H�5��H��H�D$L�` L���^g�����U����_����^��ff.����UH��H��1�SH��H���L��H��t!H�:*&H��H��H��[]�IL��f��kD��H��I��H���[H�
E��1�]�ju��f.�AWAVI��1�AUI��ATM��UH��SH��H��H��H��8dH�%(H�D$(1�H�T$�D$H�D$ � d���|$t)H�D$(dH+%(��H��8[]A\A]A^A_�fDH���b��H��H���\I�}1�L�|$ M��LE�H���	H�<$�a��H�<$H���t��H��H����H�D$�B��H�|$H�$�8H��L�$H��M����H���H���&M��tH��H��H�=;�1�L�$�	R��L�$I�jI��H�
9�L��SH�;&H�5��1�L�
��H����X��Y^H��H��tfL�|$ A�EM��M��H����AUH��H��PMD�H���E1�L����t��H���_��L���C?��XZ���@H���H�5��1��B��L���H�
����1�H�5ؾ�2o��f�L�ǿH�
����1�H�5���o���H�{�H�5��1��A��랐H�O�H�5r�1��A�����fDH�Y�H�5R�1��kA���c����[�����H����AWAVAUATI��U�>u]A\A]A^A_��H��I���J��H�}H���)r��I��H�����xJ��L��H����F��H���eD��I��H����H�}�`i��H��H����L��L��H�=¾1�� P��H��L��L��I��L��I������]L��A\A]A^A_�=�����L���H�
:���1�H�5]��m���L�p�H�
���1�H�55��m���L�D{H�
����1�H�5
��gm�������u�fDAVM��AUI��ATI��UH��H���TI��H��M��tI�$H��tH;0t^L����`����uRH����H�}@t�@H���UR����tqH���@��H��td�8t_H��L��L��L��]E1�A\H��A]A^���H�5��L���r����u���H��L��H���E��H��L��H�5���]H��A\A]A^�@o��H��]A\A]A^�@��AUH��I��ATI��H�5����UL��H���l[���E��u=H�EH��tH�M 1�L��L���H�}�<��H�}�<��H��]A\A]��;���]A\A]�f���ATH��UH��SH�� H�ZdH�%(H�D$1�H�t$H�D$H�D$�>F��H�D$H��t-L�`�[D��H���1���L��1��vP��H�|$H�D$��@��H�M H�uH�T$H�}��H�|$�za��H�}�Q;��H�}�H;��H��� ;��H�D$dH+%(u	H�� []A\��"X��f���UH��H�H�EH��t�;���}@��u[H�} H�E H��t��g��H�}(H�E(H��t�oZ��H�}8H�5�#&�oW��H�=)&�P�m��H��]H�@0��@�[p���E@�f�AWAVAUATUSH��H���I��H���5H��1�1�L���n\��I��H����L���Z`��I��H��taL��H���f����uk1�L��L��1��F��H���`��1�1�H��H��I���@����uNM��tL���If��H����9��L���_��I��H��u�H��L��[]A\A]A^A_�[N��1��9���j���@E1�1�H���L��H�5���H��H��H�D$t�L���I��H�T$H��H���J���p���fDH��[]A\A]A^A_ÐL���H�
���g1�H�5X��Oi���L�8�H�
b��h1�H�50��'i���AWAVAUATUSH��H����H��L�-,�E1��k��1�L��H��1���D��H��H��I���:����9��H�8I��H��t<f.�1�L��1��D��H��H��H������H���n8��A�GI�<�I��H��u�H��L��[]A\A]A^A_�H8���L���H�
z���1�H�5X��Oh��ff.�@UH�=y��#S��H���H��H���H��jH��H�
��L�
|��A�@�1;��ZY]�ff.�f���USH��H���d��H��%&��%&���iH��HH�C0H��NH�CH��HH�C �#^��H�=�A��E1�H��H��H���%@��H�߾H���X���PG��H�3E1�E1�PH�Ź��jH�=��1�jj�EJ��H�� H�3E1�UE1����jH�=��jj�
%&1��J��H��H�3E1�j E1����j H�=l�j@Ujjj��$&1���I��H��8H�3E1�j E1����UH�=B�jjj��$&1��I��H��(H�3E1�jE1����jH�=�j�y$&1��vI���p$&H��([]�f�H�5]$&H���A:�����ff.����S1�H��H�G01��Gl�������������H�C`1�f�CD�CFf�SH�CJf�KL�CNf�sP�CR[�AUATUSH��H�o0H����H��H���>��I���L��H��H�EH��tH90tH���]Y������H����]������M�����WZ��H��I�$H��tH90tL���Y������H���NB��H�sH��I���I��I9���H��H�59L���t8��H�C0H����N��H��P�=��H��[H��]A\A]�4��DH��[]A\A]�DL�A�H�
R��H�5��H�=����d��f.�L���H�
"��H�5f�H�=}��d��f.�L�2�H�
��H�56�H�=M��jd��f.�L���f��H�S1�L��A��26�����ff.�f�AUATUSH��H��H�0dH�%(H�D$1�H������<��H��H����H�{0��@��H�5�H��I���I���S@H��I��1�����H�$�6��L��H���h��j�H��H��E1�A� L��H�D$P��H��Y^H�D$dH+%(uaH��[]A\A]�DH�D$dH+%(uAH���H��H�5��H�=%�[]A\A]��5��@H�D$dH+%(u	H������
P��f.�AUATUSH��H��xH�0dH�%(H�D$h1�H���^�;��H��H����H�{0�?��H�5
�H��I���H���SDH��I�ĉ����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�(4��L��H���f��jL��E1�H��A� �H��H�D$P��F��XZH�D$hdH+%(u`H��x[]A\A]�@H�D$hdH+%(uAH���H��xH�5f�H�==�[]A\A]�4��@H�D$hdH+%(u	H������"N��f���ATL�%��UH��S��P�9��L��H���7e����tH��t^[]A\�f�H��t�H��1�H��E1�jL�
���1ɾ�@��H��P�=9��L��H��XZ1�[]A\�X��f.�H����H��E1�E1�1�H�5��H��H��P�8��[L��]H�ǺA\�qX�����S�h�AI���f��4I��=:�0t
=��0u6���S�[Hc‰�Hi��$I���H�� ��)ȍ�)���)���H�y��1�1��[:���f���AUATI��H�=;�U�hM��H��H��th1�H�ƿ �A?��H��H���f5���L��I���F7��1�H��tL)�H�P1�L���nS��L��I���35��H��tH���V=��L��]A\A]�D1��5���L��I����6��H��u�L��1�1�� S��L��I����4��L��]A\A]�ff.����������r^��f���ATI��H��UH��SH���g8��I��H��[L��]H��E1�1�A\�=A��ff.�f���UH��SH��H��H��(dH�%(H�D$1�H�T$H�t$�^J��A��1�E��tH�t$H�|$1��b��A��H�D$E��tH�T$dH+%(u7H��([]��H���.���[1��I��1�H���H�
ҵ1��`��1���
K��f.���AUATUH����I��H��I���[��H��I�$H��tH90tL���Q����tkH��t%��W��I��H�EH��tL9 t0L��H���]Q����u!H��H�5˽1��D0��]1�A\A]�@L��H���5��]L��A\H��A]�_��DH���H�5��1��0��]1�A\A]�ff.���E1�@��A��u���ĉD���@��H���։�D��jE��5�&�D$0PD�L$0�1��H��(�f���AWAVAUATUSH������/��	H��H�?D��E��A��A���D$A���L��D�����Q���D$f��I��H���Z�f(���0��L���3��H��A�G�H�@L�|��H��H���D<���C�S�f�f��H��D)�D)��*��*��A��f��H�s�H��f(��|^��H���X��H����W��I9�u�H���_R��H��L��[]A\A]A^A_�DL�z�H�
ڻ�s1�H�5^���[���L�>�H�
���t1�H�56��[��ff.�@����6�����AT1�I��H��UH��H�=�hL��SH���[:��I��I��L��[H��]�H�=�A\�z��f.���ATI��UH��H���:U��L��H���3��H��H��H��]A\�]��ff.�f���AT1�I��H��UH��H�=hhL��SH����9��I��I��L��[H��]�H�=c�A\����f.���ATI��UH��H���T��L��H���2��H��H��H��]A\�\��ff.�f���AT1�I��H��UH�=�H��SH���>9��I��I��L��[H��]1�H�=�A\�`����ATI��UH��H���*T��L��H���2��H��H��H��]A\�
\��ff.�f���H�5����L��ff.���UH��H�� dH�%(H�D$1���U��H��1�H��t�K��H�L$H�T$H��H��L�D$�V0��H�T$dH+%(uH�� ]��KF��ff.���H��&H��tH�y&��UH�-h&H���PA����uH�U&]����H��H���PR��H�9&]����AVAUI��ATI��UH��S�����H��H���I1����t\H�EH�8�3��H��I�$H�8�3��UL�
[g1�PA�=1�H�
^�A�t$H��v�S��2��H�� []A\A]A^�I��L���-9��M�f(H��L9�t�H��tH����'��I�^(M��tL���#(��H�5�&[H��]A\A]A^�<E��ff.����AUI��ATI��UH��S��H�����H��H���g0����tZH�EH�8�2��H��I�$H�8�2��UL�
yf1�PA�)1�H�
|�A�t$H��u�S�2��H��([]A\A]�H�p(H��L��[]A\A]�F��f���AWAVAUI��ATI��UH��SH��(�D$L�|$L�t$dH�%(H�D$1����H��H���/��H���F��H��H���/��H���:S��H�t$H��H���X��H�{(L��L���D$��'��L��L��H���Q��M��t�D$�AEM��t�D$�A$H�D$dH+%(uH��([]A\A]A^A_��C�����AWAVAUI��ATI��UH��SH��(�D$L�|$L�t$dH�%(H�D$1�����H��H����.��H���=E��H��H���.��H���ZR��H�t$H��H���Z��H�{(L��L���D$�%Z��L��L��H����7��M��t�D$�AEM��t�D$�A$H�D$dH+%(uH��([]A\A]A^A_��B�����UH���S���H��H���.��H�x(H�@(H��t�r%��H�=�&�P�qX��H��]H�@(�����H�e&H��tH�Y&��UH�-H&H���`=����uH�5&]��K��H��H���`N��H�&]����AWAVAUI��ATI��UH��SH��xdH�%(H�D$h1��p���L��H���E-��I��Hc�&I�L�kL���5��L���D$�&���l$f��(�.����5��fA~���.����=��|$���RP��H��H�l$@L�d$H��L�l$�,��H��H���]>��DL��H���V������H�|$f�)D$ ��$����t�H�t$H�{�=��H�t$0H����H�8L�t$ �2��f��f�L���*L$4�\K�*D$0�\C�/0��H�|$1�1�L�D$L���IH���L$�D$L���%Q���D$ fAn�L���d$H�|$�Y��Y\$(�D$ �D$$�\$(�Y��Yd$,�D$$�d$,�+��L��H���
V�����
���H�D$hdH+%(��H��x[]A\A]A^A_��H�|$L���.���L$�D$0H�|$�N�����L���L$��3���L$f���^D$fA~��4����L���L$�$���L$�^��D$�&����h?�����AUI��ATI��UH��S��H����H��H����*��Hcp&��t[H�EH�8�-��H��I�$H�8�-��UL�
�`1�PA�;1�H�
ΩA�t$H�
p�S�_,��H��([]A\A]�@H��L��H�t[]A\A]�3��f���ATI��UH��S�M���H��H���"*��Hc�&H��M��L��H���*��H���2��H�=�&H���pT��[L��H��H���]A\��ff.�f���ATI��UH��SH������L��H���)��HcX&H��t�H��tH�|�2���E[]A\�@��ATI��UH��SH�����L��H���_)��Hc&H��t�H��tH�|�"���E[]A\�@��AWAVAUATUSH��H��XH�|$L�d$ L�t$L�l$dH�%(H�D$H1�����H��H����(��L��I��Hc�&I�,H�u��3���*H�sH��u_H�s H��ulH�s(H��u}H�}L����O��L��L��L���:����twH�\$L�|$H�sH�;H��t�H�C�8��H�sH�;H��t�H�C�8��H�s H��t�H�C H�{�8��H�s(H��t�H�C(L���p8���n���H�}��+��H�=�&�P�~R��H�|$�P(H�D$HdH+%(uH��X[]A\A]A^A_��B<��f���UH�����H��H���'��Hca&H�|�WL��H�=X&�P�R��H��]H�@0��ff.����AWAVAUI��ATUH��SH��HdH�%(H�D$81��s���M����H��I�EH��tH;0tL���aB�������D:��H��H��tH�EH��tH90tVH���6B����uJH�y�H�5�1��!��DE1�H�D$8dH+%(�nH��HL��[]A\A]A^A_��Lc5a
&H�\$L�d$H��M�I�v�1���fDH�D$H9(t�1�L��H���y8����u���I��H��I����R��L��H���Z&��H��H���O���0I���K��E1�E1�L��H�(H��H���I��H�XH�5B��5��E1�E1�L��H��H����H�-ąI�GH�5a��o5��E1�E1�L��H��H��H����I�G�P5��E1�E1�L��H��L��H��I�G �15��I�~L��L��I�G(�>.��I�>L����&��L��������fDH��H�5r�1�E1�����~�����9��ff.���AWAVI��AUATUH��SH��XdH�%(H�D$H1��C���M����H��I�H��tH;0tL���2@�������8��H��H��tH�EH��tH90t/H���@����u#H�D$HdH+%(��H�6��Lc=Y&H�\$ L�l$H��L�d$M�I�w�/���f�H�T$H9*t6L��L��H���h6����u�H�D$HdH+%(�;H��X[]A\A]A^A_ÐH�T$�G��H�|$H���)$��H�T$I��H�rH��uHH�rH��ubH�r H��u|H�r(H����I�L���-B��I�?L���"K��L���Z���q���DH�BH��H�T$�4��H�T$H�rH��t�H�BH��H�T$��3��H�T$H�r H��t�H�B H�zH�T$��3��H�T$H�r(H���i���H�B(L���3���T���DH�D$HdH+%(u#H���H��XH�5�1�[]A\A]A^A_�E���7����Hc�	&UI��H��H�|L���4��H��tH�0H��]���L�o�H�
��1�H�5��?J��ff.�@��ATUSH��H��@dH�%(H�D$81���H����H��H�H��tH;0tH���=����tgHc	&1�L�d$H�tH�\$H���g-���DH�D$H��H�0�7��H��1�L��H��� 4����u�H�D$8dH+%(u%H��@H��[]A\�H�i�H�5r�1�1�������R6��f���H�-&H��tH�!&��UH�-&H���`1����uH��&]����H��H���`B��H��&]����AUI��ATI��UH��S��H�����H��H���W!����tZH�EH�8�#��H��I�$H�8�#��UL�
iW1�PA�\1�H�
ǛA�t$H��f�S�"��H��([]A\A]�H�pH��L��[]A\A]�7��f���UH����H��H���� ��H�x �oE��H�=@&�P�.K��H��]H�@0��@��ATH� �1��I��H��tH�����L��A\�ff.���H��&H��t��H���o���1�H��1�����H��&H������H�
&H��tH�&��UH�-�&H���/����uH��&]����H��H���@��H��&]����A�ɉ�5�&A��1�1��'��D��H��+����H��#'����H��s;����H���1����H��#)����H��c>����H�������5R&1�1��&��ff.�f���H��53&1�1���&��f.���A��H��5&I��1�1��&��@���5�&1�1��&��ff.�f���I��H��5�&A��1�1��t&��@��H��(dH�%(H�D$1��$H��D�D$H�D$���H�D$dH+%(uH��(���2��D��H��5C&1�1��
&��f.���H��5'&1�1���%��f.���H��5&1�1���%��f.���H��I��H��5�&AP1�A��1��%��H���f���H��5�&1�1��z%��f.���H��5�&1�1��Z%��f.���H��H��5�&1�dH�%(H�D$1�L�D$�#%���D$H�T$dH+%(uH����1��D���5Z&1�1���$��ff.�f���H��H��57&1�dH�%(H�D$1�I���$��H�$H�T$dH+%(uH����71�����H��H��5�&1�dH�%(H�D$1�I���e$��H�$H�T$dH+%(uH�����0�����SH���3���1�H��1����H�X[��H�
&H��tH�&��UH�-�&H����+����uH��&]����H��H����<��H��&]����AWAVAUATUH��SH��hdH�%(H�D$X1�H�\$0�q���H��H�����L�pL�t$����H�9����1���'��I�6H��L�t$ I���&��H�D$(H�D$�=@H�D$ L�@@L�hL�x8L�D$�E ��H��H���J��L�D$L��1�H��L��A��H�t$L��H���'-����u�H�D$H�8��?��L������H�=�&�P�}E��H��P0H�D$XdH+%(uH��h[]A\A]A^A_��C/����AWI��AVI��AUI��ATE��UH��SH��8H�D$pL�D$H�D$H�D$xH�D$dH�%(H�D$(1��1���H��H���v��L��H�=9�L��H��1���#��I��H�EL��H�8�]+��H��tH���@�����?��H��H���K��H�C�")��L��H��7��L��H�C��!��H�|$H�C �-��L��H��D�c0H�C(H�D$L�{H�C8H�D$H�C@H�EH�8�1��H�=�����H�{PH���%(��A��umA���EH�k�\2��H��H����D��H��H����`5��H��H���� ��I��H���L�|$$H�5���L���D$$L���E���D$$��tb1�����H�CHH�D$(dH+%(���C0H�K1�H�{L�K L�C�5�%�D$xH�C(H�D$pH��81�[]A\A]A^A_� ��fD�3��H��I�$H��tH;0tL����3�������/��H��H����C��H��H��t#L��H�5����D$$�D���|$$���=�����E��H��H���C��H��H����L��L�%6�%���E1�M��L��H��H�
�H�5u�1��(��H��L�L��S�H��L�
���1�H���,)��ZYH�D$(dH+%(��H��8H��[]A\A]A^A_��.�����H��I�$H��tH90tL����2�����Z����+�����H�5���L���D$$�	D���t$$���-����F����L�ǎH�
��1�H�5����>���H��H�5��1��s�������+��f���AUI��ATI��UH������L��H�����L��H��H�=ΎL�h1�� ��I�}H��H���'��H��I���,��M��t]L��A\A]����f.�]A\A]�f.���AVI��AUI��ATI��UH��H���@���H����H��H�EH��tH90tH���1������M����1�H��L��L���
>��H�5����H��H�����H���C��H��t~L�����H�O�%H��H���t.��H�5���H���:��H��tH��H��]A\A]A^�]
��DH��]A\A]A^�@H���H��H�5F�1�]A\A]A^�����H�5a�H������n���@H�o��ff.�@��AUI��ATI��UH��SH��H������H��t\H��H�H��tH90tH���t0����t@H�CH��H�8�A&��H��tLH��H�xPL��L��[H�5ߏ]1�A\A]�>��f.�H��H��H�5�1�[]A\A]����H�������AUI��ATI��UH��SH��H���a���H��t\H��H�H��tH90tH����/����t@H�CL��H�8�%��H��tLH�xHH��L��1�[H��H��p]A\A]���f.�H�7�H��H�5�1�[]A\A]�a���H�5������AWAVAUA��ATI��UH��SH��dH�%(H��$�1����H���aH��H�EH��tH90tH���.�����@L�uL��I�>�$��H��H���UA���3A����H�{P�8��H��I���m1��H����L����
��H�{H�c��I��C0�H�=ɊL�|$���L��H���!��H�S L��1�H�5��L���2<��L�K8L�C@L��L�L$L�$���H�[I������H��H������1�H��L�$H��L�L$L��A��I�>L���1��H��$�dH+%(��H�Ĩ[]A\A]A^A_��H�{HL��H�5���7�������H��$�dH+%(�8H�P�H�ĨH�5�1�[]A\A]A^A_�s��H�{����H�s 1�L��H��I���#9�����H��H�����1�1�L��H������L���l	���������H��1���1��9��L�C@L�{8I��H�[L�$���H��H�����1�L�$L��H��H��A��L���|��H��$�dH+%(uXI�>H�ĨL��[]A\A]A^A_�/��D���H�<�����p���H��$�dH+%(uH�B������%��ff.����AUI��ATI��UH����M����H��I�$H��tH90tL���,����tkH��t%�y2��I��H�EH��tL9 t0L��H����+����u!H��nH�5�1���
��]1�A\A]�@L��H�����]L��A\H��A]�/��DH�_�H�5��1��
��]1�A\A]�ff.���H���%H��tH��%��UH�-�%H��������uH��%]�����H��H����0��H���%]����AUATI��UH��SH��H��dH�%(H�D$1�H�$�l���H����H��H�EH��tH;0tH����*�����rM��t%�(1��I��I�$H��tL9(t7L��L���*����u(L��mH�
ʓ��1�H�5Ї��6���H��t+L�P�H�
����1�H�5���6��f.�L��L�����H��H���.��I���	��H�<$�����������L�-#�%H�}8L����"��I�$I�$H�}(H�E(H�E8H��t�%��I�D$�5N�%H��1�I�D$H�E(1��M��I�|$I�D$H��t�u%��I�<$L���y"��L�����H�<$H��t�#��H�D$dH+%(u4H��[]A\A]��L�G�H�
����1�H�5���5���j"��f.���ATUH��H����H��I���q/��H��H�EH��tH90tH����(�������[���H��M����I�$H��tH;0tL���(��������G2��H���%H�5q�%H�=��%I��H������I�D$���I�|$I�$���H��L��H��H��]A\���fDL�b�H�
����1�H�5���4���L�9�H�
j���1�H�5`��W4�����SH���s���H��tNH��H�H��tH;0tH����'����t2�{@��uH��H�5������&���C@[�f��9����f�L�ÇH�
����1�H�5؄��3��ff.�@AUATUH��SH��H������H����H��H�H��tH;0tH���:'������H����1�H�5��H��1��F��H��I���+0��1�1�1�H��I���*)��H��H��t8H�Ǿ��E$��H�5
BH��H��A�H�����8��H�{ H����0��M��tL������H��L��[]A\A]���L���H�
z���1�H�5Ѓ��2���H��1�[]A\A]���ff.�@��ATUH��H�=_�%S���H�E �05��H��H��������H�0H��tI��1�H������CI�4�H��H��u�H���%H�5��%H�=��%H���,��H�E(H������[1��H��A�H�����H�EH��H�5�@�7���C��[H�E8]A\�f.���H���%H��t��H�����1�H��1������PH��H���%�
��H�5��%H������H���%H���@��SH�����H��t&H��H�H��tH;0tH���%����t
H�C8[�@H��H�5��1�����1�[�ff.�@��ATI��USH���=���H��thH��H�H��tH;0tH���$����tLH�[8H��u�V�H�[H��tLH�+H���&��L��H����6����u��1��[H��]H��A\�)	��f�H�G�H�5�1��;��[1�]A\�@��UH��SH��H�����H��t>H��H�H��tH;0tH���#����t"H��t2H�{(H������H��[H��]���H�σH�5:�1�����H��1�[]�f.���UH������H��]H���g������H���%H��tH���%��UH�-��%H��������uH���%]��[��H��H����(��H���%]����AVAUATI��US���L��H������L��H�����H�����H�=g�%H��I���<2��L���L�����I��E8��f��f(�f(�f(����L��I��H�����L����	��1��E8�L��H��E1��	ЈE8�A���O��D��H����2��D�e8L��H���4��[]A��H��A��D��A\A]A^�)��f.�H���#��H���
��1�H9�u�1�L���o����t���D��ATI��UH��H���j���L��H�����H�}`H�p0I����$���5�%L��1�H��1��j��H��1�]A\���AUATUSH��dH�%(H��$�1�H����H��H�����H��H�EH��tH90tH���o!�����WH���.H�����H����1��H��I������H��I���x���L��H�T$H���.��L��A���=(��E����H�D$(H��H�p���H��H�����b���1�H��1��f���H�X0I�����L��H��I�����H��H���A��1�1�H�L$H���/��1�1�H�L$H���o"��1�1�H�L$H���	��H������L��L��A���D$D$D$D9�A�D$8�ƒ�	�A�D$8�&��H��$�dH+%(uQH�Ĩ[]A\A]�DH�m�H�5b�H�=I~����1��f�H�a�H�5B�H�=)~���1���-��ff.�f���AWAVAUATUH��SH�����!t$E1���H��D��[]A\A]A^A_�fDH�G(H��H;B ��E1�H;E(u�L�eXM��t]H�C ��M�d$M��tFM�,$I;E(u�I�U�I�}H�s8H9�HN�H)�I} Lc�L������M)u���A��]���D�5Z�%H��1�1����H���V���9����H�W@H����H����H���@���L�eXH�FHH�v M��u�K@M�d$M��t=M�,$I9u(u�I;Eu�I�} ���L�����H�}XL���Q���L��H�EX�u��H�s H�}`�H��H��H���<���L�CH�5��%1�H��1�A���
�����L�gHH�}`L�����H�������H�}8L���S���I��H������5A�%H��1�H��1��
�����L��H�����H���"��I���2/��H��M���2I�H��tH;0tL��������E1�E1�H��L��H�����H�5�}�����	��L��H��I���G��L��H�����L��L���1��H������H����H�}`L��L��A��
��L���S"���v���H�v H�}`���H��H�������L�eXH�SHL�{PL�sXM��teH�C �f.�M�d$M��tKM�,$I;E(u�M;uu�I�} H�T$H�$���L������H�}XL�����L��H�EX�
��H�$H�T$M����H���5��%M��H��RL��}1�1�A��	��Y^���I�u(H�}`�7��H��t'H��M�E A�uH��M�M1�1�H��5z�%����XZI�} �B���L���:���H�}XL������L��A�H�EX����?����0H�$�%��H�$I�I��H�C I�T$I�D$(M�|$M�4$M�|$��
��I�T$H�}XL��I�D$ A���M��H�EX����L������g����5��%L��1�H��A�������f���AVAUATUSH��H��0dH�%(H�D$(1����H���GH��H�H��tH90tH��������'���H��H������H���7��H�5�{H��H���
��H�5{H��I������H��I������H��L�s0�\+��E1�1�L��H��H�D$L��E1�I��������PH�D$PH�D$(PH�D$PH�D$@PAU�|���H��0H�����$��	�uJL�d$L9l$ u^�|$uWH�t$H��tM1�L���#*����tH�|$H�t$�p���H�|$I�����H�D$(dH+%(u_H��0L��[]A\A]A^�M��t�L��E1�������fDH�]zH�5��E1�H�=�w�����f�H�|$E1���������ff.����SH������H��t.H��H�H��tH90tH���v����t�C8[���f�H��yH�5R�H�=yw�F���1�[�f���AWAVAUATI��UH��SH��8dH�%(H�D$(1����L��H������H��H���]��������C8u/H�D$(dH+%(��H��81�[]A\A]A^A_�f.�L�����H��I���M+��H�t$H��I�����L���#��D�L$L��\$D�|$D�t$D�L$����L��H�����H���)��H��A��D��jD�L$H��D��H���C���L$(�T$$L��D�D$,�t$ �
��XZ�4���f��H��f(�f(�f(����H���{���H��� ������)��f���AVAUATUH��H���dH�%(H��$�1��c���H��H���x���H��I�������t�E8u+H��$�dH+%(��H���]A\A]A^�DL�����H��I���'��I���}��H��H������H����
��H��L��H�����H���D$���L���D$HH�D$0H�D$H�D$8H�D$@�4�H�t$0L��1�L�D$���;���L�������;����	��f���AWAVAUATI��UH��S�H��(dH�%(H�D$1�I����H��H���P���L��H��I���b
���$A9$u1ۋD$A9D$������I�ŋD$A9D$thH���@�������H�=��%L���y%��H��L���H����������H�D$dH+%(��H��([]A\A]A^A_�f.��D$A9D$u���u�H�=:�%L���%��L��H����@L���������h���H������H�����1�L��H���c���F���fD���H��H���8���H�������u$���G���A�F8�<���L���a����/���@H���`���H�����1�L��H������	����Q�����AVAUATI��UH��SH��H�� dH�%(H�D$1��R�H���yH��H�EH��tH90tH��������X�c���H��H���x���H�����L�u0H���4%��H�$H�D$H��I������H��H����L��H��L�����H���w��H��tH�H�,$M����I�$H��tH1�����uI��@��tfDL��H������u@��u�1�L���y��H�,$I�$H������H�l$H��tNH��tA1�����uI��@��t�L��H������u@��u�1�L���!��H�l$H�H���q��H�D$dH+%(uWH�� []A\A]A^�@H���v����y���f�H�D$dH+%(u&H�� H��sH�5{[H�=dq]A\A]A^�*��e��D��H�E�%H��tH�9�%��UH�-(�%H���p����uH��%]����H��H���p��H���%]����UH��SH�����H��H���s���H��H�����H�{X���H�{`���H�=��%�P��!��H��H�@0H��[]��fD��AUI��ATI��UH��S��H���"���H��H��������tbH�EH�8�V���H��I�$H�8�G���UL�
-A��PH�
p1�H�X<A�t$�H�=pS���H��([]A\A]�fD�p@H��L��[]A\A]�
��ff.���H�����1�H��H��1�����AWAVAUATUSH��dH�%(H�D$x1�H�8tTH�arH�5xE1�H�=zo�G��H�D$xdH+%(��H�ĈD��[]A\A]A^A_��I�����H��I�$H��tH;0t/L������A�ƅ�u H�rH�5�wH�=o����@I�|$8t(H��qH�5�wE1�H�=�n���^���fD�C��I�D$8H��H������H��I�����H��H�D$�;�H��H��������H��������H�=cq��1����1�H��I������L��I�D$�D�I�l$0�PH�����H����L���"���I�|$0H���4����I��H����I�|$0����H�5�pH��I������I�|$8H����!��H����I�|$8�!��H�����H�����L��H�D$�*�L��I��� ��jH��E1�H��A� � L��H�D$P����XZL��葸��H���y�H��I���N��I�T$L��L��A������d�A�ƅ���L�t$H�5�oL���D$!I�FH�D$0���I�t$L���D$@ H�D$8��H�D$H�3��L��H�D$P�f�I�nL��H�D$`H�D$XA�H�D$h�=��L�D$1�H��H�����1�H�=3o�(��L��H������1�H�=2oI�D$ �
��L��H�����L��H�5e�L��I�D$(����H���8��H���`�I�D$0I�D$8����f�H��RH�5�tH�=l�������I�|$8���H������H���*���fDH�3lH�5�tH�=�k���H�������ff.����AUATU�2��H�����H������H�=�n��1�����H��H��I���t���L��I���i�H������L��H�����]A\H��A]��������UH��S��H�����H��t_H��H�EH��tH90tH��������tB9]@u
H��[]�fD�]@H�����H��P��H��H�5Rj[H��]�{��H��H��mH�5&s[H�=�j]�����AVAUI��ATI��UH��S���D���H��H���)��tdH�EH�8�x�H��I�$H�8�i�UL�
;'A��PH�
3j1�H�z6A�t$�H�=7jS���H�� []A\A]A^�fDL��I���%�[L��]��A\A]A^�����AVM��AUI��ATI��UH��SH�����H����H��H�EH��tH90tH�������tzH�}DH���.����u*�H��[�EDA�$�EHA�E�ELA��EP]A\A]A^鑴���H�}HL��������t�H�}LL��������t�H�}PL��������t�[]A\A]A^��[H�9lH�5qq]H�='iA\A]A^���ff.���SH�����H��t&H��H�H��tH90tH���
����t
�C@[�DH��kH�5�pH�=�h��1�[�f.���AUI��ATUSH��H���G��I��H��tOH�H��H�hH���M����t!I��I��L��H�to1�1�E1����H��L��[]A\A]�f.�L��1�H�Rk1����H��L��[]A\A]�f.���AUI��ATUSH��H�����I��H��tGH�hH��H�������t!I��I��L��H�o1�1�E1��K�H��L��[]A\A]�DL��1�H�,o1��� �H��L��[]A\A]�f���AWAVI��AUI��H�=�jATUSH��(dH�%(H�D$1�I���
��H��H����DH����H��H��thH�$�{.t����H�{�
L���I����
��H��A���u�H�$H��t��8u�Hc�H9�u�H����9�t���L��A�Յ�t�H�D$dH+%(��H��(H��[]A\A]A^A_���fDH���s����uOH�l$H���tD��~1��@��9�t��L��A�Յ�t�H�D$dH+%(u#H��([]A\A]A^A_�fD������-��ff.�f���H��1�H�=����z���H��m1�1��@H���q����ATI��UH��H���j��H��H���_�H���g�L��H���	��H��H��1ɉ�]H�5�h1�A\������AVI��AUATUH��H��膳��I�����L��H����H��H�����H������L��H����� ���E1�E1�L��L�0H��H�����I��L�hH�5hhH�h�.���L���6
��I�FL��H��H�x�3�H��L��H��E1�]E1�A\H��p��A]H�5)hA^���f���UH�����H���%���%����H��P���H��H����H�E H�<��H�E0��A��H��gH�5�gH��H�=9$���H��H�Q�%H�R�%������H������UE1�E1�jH��1ɺjH�=�g1�j�#���H�� ���%���H��E1�E1�jH��1ɺjH�=pg1�j���H�� ���%]�H�5��%H��������ff.����H���3���H���+��H���S��H��H���'����AUATI��U����H�����H�����H��H��u�fDH����H��H����H���D�����u�H��L�����I��H��tmH��L���2���L��H���G���I�|$I9�t;H��t���M�l$L����H�-��%�PL���b�H��H�����M��t2]L��A\A]��DI�|$H��t��E1�I�D$�]A\A]�f.���H����@ATUH��H��H��tQ�<���H��1�H�5�!I��1����L��H��H�����I��H��tL������I��H�����H��L��]A\�f�H��`H�5�i1�E1�����fDAUATI��UH��H� �|���H����H��I���x��H�} L�����L��L������H��L��E1�UL�
�1�1Ҿ���L��E1�1�L�
21ҾH�,$���L��E1�1�L�
C1ҾH�,$���5A�%H��1�1���XL��Z]A\A]�2��f�]A\A]�f.���ATI��UH��H�����L��H����H��H��H��]A\���ff.�f���AVH��AUI��ATI��UH�����H��t6H����H��莑��H��H�����H��I���x��M��tL��L������-r�%L��P��H��L��1�H�lj�1�]A\A]A^��f���AUATUS��H������H���3���I��H��tKH���L�mL��褿��H��H��tLH����H�RH��t79u�����M��uH�mH��u�E1�L������H��L��[]A\A]�������f�AWAVAUI��ATI��UH��SH��f�H��H�����H��H��u�H�������tH��������tI�} H�����I��H���=H�������������H��I������I��H����H�5�bH���"����L��H�=
c1���H��H�����L��H��I�����I��H���$H��t#L��L�$�C���H��H�����L�$����L���T��I��H���y��M����H���8��H��H��t����I��H���gH����H��H��t���I��H���FH�������u"H�������ƅ�~L������I��H���H���h���H��H��ttL���X���I��H��tdM�7L���
��H��H�������uAL������H��H��t1�!�I��詎��L��H�����L��H�$���L�$M����M�M��u�H���d
��H��H��tL����H��H��t/I��M�7L�������uI�} L������I��H��u8M�M��u�H���H���H������I��H��u(H��[]A\A]A^A_�H�����f�L�����I�} L��L���)�L��L��E1�E1�H��H�5�`���L��L��E1�E1�H�wH�5�`���L��L��E1�H�����E1�H�5�`��L��L������5�%L��1�H��1�[]A\A]A^A_����H���H�L��H��I���*��I��H���aH���V���L��H��H�$���L�$H��I����L������I���v����H������L��H��H�D$����I��H����L��L�$�`~��H��H�����L�$��u�L��L�����I��H���8L���.~��H��H������BH�t$L���~��I��H����L���}��H��H������mL�����I�����H�5�^L���X�����6���H����L��H��I�����I��H������L��L�$�B���I���*���M��L�$I��tyI�H��tL;8tL��L��L�$���L�$��tUL��L��L�$�[�H���3��I��H��t6H�5a^H������L�$����H�5Y^L����L�$������1��^�����L��L���^��I��H����H�������L�����I�����L��L��L�$�*��L�$H��I��ta1���L��L�����I��H���]���H�t$L�����I��H���x���H�������]���1��:���L�����I��H�������1���L��L�����H��H��t�I��1��I���H�����L��H��H�D$�h��I��H������H��������W���H�t$L���a��I��H���k���H��������������ATI��UH��H�����H��H�����L��H��H���1���H��L���f���H��H��]A\������ATI��UH��H��蚽��H��H���o��L��H��H�����H��L������H��H��]A\�7������ATI��UH��H���J���L��H�����H��H��H��]A\����ff.�f���ATI��US��H����
��H������H�
)�%H�5Ҿ%1�H�=��%H���A�E1�E1�L��I�D$ H�<���H�5H����蘨��H���
��H���8	��H��t'H��H��DH�3L���-���H�[H��u�H�����W���H���O
��L��E1�E1�H��H�,���H�5�=H����[L��H��E1�]E1�A\H����H�5�=��f.���AWAVAUATUH��SH��H��H�?dH�%(H�D$x1�L�l$PH�D$H���H�T$0H�t$,H��I����D$,H�;H�D$P�D$X�D$0�D$\�y��H�L$8H�T$4L��H��L�D$<I����H�L$HL��L���D$<��C<����H���L��I���x�H��I������I��H���#H����L���D$���$�%�H���
���$�t$H��H�D$���H��H�D$����H�$����L�$H��L��L�L$���f�۾(�(�(�H���5�H�|$���L��1�H��H�$���u��H�|$H���x��f��H�4$�=4�4(�H��(�(�(�(����H�<$�P���H�|$���L�D$1�L��L�$�3���H�<$H�C@�&���L������H�sH1�L���CP����CH�KLL�����H�T$DH�t$@L��I������f���SP�CH�*L$@�Y��\�f���*L$D�CH�CL�Y��\��CLM����H�t$`L�����L���)��f��SH�%(3�*t$`�=�2(�f���*l$d(��\��Y�(�T�.����KL�\�(��Y�(�T�.�wx�^��^��X��SH�X��CLH�G�%L��H���\��H��tH���O��H�|$HH��t���H�D$xdH+%(��H�Ĉ[]A\A]A^A_�f.��,�f��U��D(2�*�(���AT��\�V�(��U���D�,�f�U��D
�1�*�D(��D��ET��A\�(�V����f�H�t$HH��H�D$H���H���*����-����w����AWA��AVA��AUATI��UD��SD�˃�H��XH�?�t$dH�%(H�D$H1�����t$D�|$4L�|$0H�L$ L�D$$H��I�ʼnt$0H�T$L��D�t$8�l$<H�D$(���T$ �t$1�����H��H���E��H��A����H��D��D�KH��H�D$0L��L��P�D$4A���O��ZY��tAI�l$ ��H�|$(I�D$H��t����H�D$HdH+%(uEH��X[]A\A]A^A_�@H����H�D$(1�H�PV�H�H1����H�|$(H��u���!���H��H���app->running_state->windows../src/shell-app.capp->info == NULLsrc/org-gtk-application.csrc/switcheroo-control.cShellAppShellAppUsageShellGlobalhandle-activatehandle-openhandle-command-lineBusyresize-modeapp-paintableborg.gtk.Application(ssv)net.hadess.SwitcherooControlShellAppSystemswitcheroo-control vanishedHasDualGpuhas-dual-gpuNumGPUsnum-gpusaa{sv}GnomeShellPluginapplication-x-executableapp->running_state != NULLcontext %s="application-statescorelast-seenUnknown element <%s>ShellBlurEffectbrightnessShellEmbeddedWindow/usr/share/gnome-shellGNOME_SHELL_DATADIRGNOME_SHELL_JSimages/%s/LEorg.gnome.shell:resourceresource:///org/gnome/shellsearch-pathShellActionModeShellAppStateShellAppLaunchGpuShellBlurModeSigmasigmaBrightnessBlur modeShellSnippetHookShellNetworkAgentResponseShellOrgGtkApplicationShellOrgGtkApplicationProxy(@a{sv})Activate()(^ass@a{sv})Open(o^aay@a{sv})CommandLine(i){&sv}g-interface-nameg-object-pathg-connectiong-bus-typeNo property with name %sas(sa{sv}as)PropertiesChangedswitcheroo-control appeared/net/hadess/SwitcherooControl(ss)GetApplication stateBusy stateApplication idGIconApplication Action Groupaction-groupDesktopAppInfoapp-infonotify::busySHELL_IS_APP (app)notify::iconprogramUnknowngiconfallback-app-iconactionsapp->running_state->muxerwinapp.new-windowSingleMainWindowX-GNOME-SingleWindowpropertyapp-state-changedinstalled-changed../src/shell-app-system.cStatusChanged(u)../src/shell-blur-effect.cShellBlurEffect (brightness)ShellBlurEffect (blur)ShellBlurEffect (downscale)ShellBlurEffect (background)ShellBlurEffect (blit)ShellBlurEffect (final)self->actor != NULLSHELL_IS_BLUR_EFFECT (self)the_object == NULLSHELL_IS_GLOBAL (global)glXQueryExtensionsStringglXQueryExtensionGLX_INTEL_swap_eventglx.swapCompletewindow-managernotify::user-timenotify::skip-taskbarapp->running_state == NULLworkspace-switchedwindow:%dstate->refcount > 0app.quitPrefersNonDefaultGPUa{s*}DefaultEnvironmentFailed to launch “%s”app->info != NULLgnome-.desktop  <context id="">
    <application%u/>
  </context>
</application-state>
notify::focus-apporg.gnome.SessionManagerg-signaluserdatadirapplication_stateorg.gnome.desktop.privacychanged::remember-app-usagenotify-errorlocate-pointeruserThe session mode to useSession Modesession-modeScreen width, in pixelsScreen Widthscreen-widthScreen height, in pixelsScreen Heightscreen-heightMetaBackend objectBackendbackendMetaContext objectContextDisplaydisplayWorkspace 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-timestampThe debugging flagsDebug Flagsdebug-flags../src/shell-global.cexit_statusiplatform_dataargumentsaayhinturisfedora-mozilla-debian-GNOME Shell0.1VariousGPLv2+net-hadess-switcheroo-controlorg-gtk-applicationSHELL_NETWORK_AGENT_CONFIRMEDconfirmeduser-canceledinternal-errorSHELL_SNIPPET_HOOK_VERTEXvertexvertex-transformSHELL_SNIPPET_HOOK_FRAGMENTtexture-coord-transformlayer-fragmenttexture-lookupSHELL_BLUR_MODE_ACTORSHELL_BLUR_MODE_BACKGROUNDbackgroundSHELL_APP_LAUNCH_GPU_APP_PREFapp-prefSHELL_APP_LAUNCH_GPU_DISCRETEdiscreteSHELL_APP_LAUNCH_GPU_DEFAULTdefaultSHELL_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_ALLall_g_value_equal() does not handle type %sG_VALUE_TYPE (a) == G_VALUE_TYPE (b)prop_id != 0 && prop_id - 1 < 1prop_id != 0 && prop_id - 1 < 3org.freedesktop.DBus.Properties.SetError setting property '%s' on interface org.gtk.Application: %s (%s, %d)Error setting property '%s' on interface net.hadess.SwitcherooControl: %s (%s, %d)Missing attribute id on <%s> elementCould not load applications usage data: %s../src/shell-blur-effect.c:205%s: Unable to create an Offscreen buffer  cogl_color_out.rgb *= brightness;                                       
uniform float brightness;                                                 
%s/gnome-shell/runtime-state-%s.%sCould not get GPUs property from switcheroo-control: %sShellOrgGtkApplicationSkeletonMethod %s is not implemented on interface %s[generated] _shell_org_gtk_application_emit_changedorg.freedesktop.DBus.PropertiesShellNetHadessSwitcherooControlShellNetHadessSwitcherooControlProxyShellNetHadessSwitcherooControlSkeletonCould not get switcheroo-control GDBusProxy: %sGot switcheroo-control proxy successfully[generated] _shell_net_hadess_switcheroo_control_emit_changedThe desktop file id of this ShellAppThe GIcon representing this appThe action group exported by the remote applicationThe DesktopAppInfo associated with this app[gnome-shell] idle_save_application_usage%s:%d: invalid %s id %u for "%s" of type '%s' in '%s'!(app->state == SHELL_APP_STATE_RUNNING && state == SHELL_APP_STATE_STARTING)ShellBlurEffect (actor offscreen)ShellBlurEffect (actor texture)SHELL_IS_EMBEDDED_WINDOW (window)GL buffer swap complete event received (with timestamp of completion)resource:///org/gnome/shell/ui/init.jsExecution of main.js threw exception: %sapp->running_state->session != NULLapp->state == SHELL_APP_STATE_STOPPEDCould not apply discrete GPU environment, switcheroo-control not availableCould not apply discrete GPU environment, no GPUs in listCould not find discrete GPU in switcheroo-control, not applying environmentCould not save applications usage data: %s<?xml version="1.0"?>
<application-state>
/org/gnome/SessionManager/PresenceMetacity 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 paintCompletedTimestampD-Bus Proxy for switcheroo-control daemonProvides GNOME Shell core functionalitySHELL_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_GLASS �������@�������`������������������������������������������������������������������������������� ���������������������������������������P�L����l������������������������������������������������L��������������������|��X��X��0X��PX��pX���X���W��D����������ܭ����������|���<�������̬����������l���L���,����������������\���shell_global_set_stage_input_region_shell_global_init_shell_embedded_window_unmap_shell_embedded_window_map_shell_embedded_window_allocate_shell_embedded_window_set_actorshell_blur_effect_set_modeshell_blur_effect_get_modeshell_blur_effect_set_brightnessshell_blur_effect_get_brightnessshell_blur_effect_set_sigmashell_blur_effect_get_sigmashell_blur_effect_paint_node_shell_app_system_notify_app_state_changedshell_app_disposeunref_running_state_shell_app_remove_windowcreate_running_stateget_application_proxybusy_changed_cbshell_app_on_ws_switchshell_app_on_skip_taskbar_changedshell_app_sync_running_stateshell_app_on_user_time_changedshell_app_state_transitionshell_app_open_new_windowshell_app_activate_fullshell_app_update_window_actionsshell_app_get_iconwindow_backed_app_get_windowshell_net_hadess_switcheroo_control_skeleton_set_propertyshell_net_hadess_switcheroo_control_skeleton_get_property_shell_net_hadess_switcheroo_control_skeleton_handle_set_property_shell_net_hadess_switcheroo_control_skeleton_handle_get_property_shell_net_hadess_switcheroo_control_skeleton_handle_method_callshell_net_hadess_switcheroo_control_proxy_set_propertyshell_net_hadess_switcheroo_control_proxy_get_property_g_value_equalshell_org_gtk_application_skeleton_set_propertyshell_org_gtk_application_skeleton_get_property_shell_org_gtk_application_skeleton_handle_set_property_shell_org_gtk_application_skeleton_handle_get_property_shell_org_gtk_application_skeleton_handle_method_callshell_org_gtk_application_proxy_set_propertyshell_org_gtk_application_proxy_get_property_g_value_equal�@��@�?K�??�@���@�C����pidreplace_contents_asyncbytes != NULLG_IS_FILE (path)ShellGLSLEffectShellInvertLightnessEffectShellPerfLogShellScreenshotShellTrayManagerShellGtkEmbedmessagedescriptionwarningpassword-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-confirmShellKeyringPromptstripped_label != NULL../src/shell-keyring-prompt.cG_VALUE_HOLDS_STRING (value)value != NULLshow-processes-2ShellMountOperationEvent names can't include '"'perf.setTime\",
  [%li, "%s"][%li, "%s", %i][%li, "%s", %li][%li, "%s", "%s"]../src/shell-perf-log.cinitiatecancelscreenshot-taken%c%FT%T%zpngtEXt::Creation Timegnome-screenshottEXt::SoftwareShellSecureTextBufferShellSquareBinShellStackThe icon's window titleTitleThe icon's window WM_CLASSWM Classtray-icon-addedtray-icon-removedBG Colorbg-color/proc/self/cmdlinefailed to reexec: %slaunchedglobal->work_count > 0klass->base_pipeline != NULL../src/shell-gtk-embed.cShellTrayIconPasswords do not match.GNOME_KEYRING_PARANOIDPassword cannot be blankself->task != NULLself->mode != PROMPTING_NONEperf.statisticsCollectedglFinish[ ,
    "statistic": true } ]screenshot != NULL../src/shell-screenshot.cafter-paintG_IS_OUTPUT_STREAM (stream)shell_screenshot_screenshotshell_screenshot_pick_colorG_IS_TASK (result)color != NULLtext-changedself->task == NULL../src/shell-tray-icon.c../src/shell-tray-manager.cclutter.stagePaintStartclutter.stagePaintDonenotify::widthnotify::heightStart of stage page repaintPaint completion on GPUnotify::key-focusnotify::focus-windowx11-display-closingui-scaling-factor-changedglobal->plugin == NULLShellEmbeddedWindow to embeddestroywindow-createdCapturing window failedmapwindow != NULL_NET_WM_PIDx11-display-setupstyle-changedCould not delete runtime/persistent state file: %s
!cancellable || G_IS_CANCELLABLE (cancellable)Could not replace runtime/persistent state file: %s
Failed to open runtime state: %scogl_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);
Text field for confirming passwordg_async_result_is_tagged (result, shell_keyring_prompt_password_async)g_task_get_source_object (G_TASK (result)) == promptg_async_result_is_tagged (result, shell_keyring_prompt_confirm_async)g_task_get_source_object (task) == promptOnly supported event signatures are '', 's', 'i', and 'x'
Maximum number of events defined
Duplicate event event for '%s'
Discarding oversize event '%s'
ShellPolkitAuthenticationAgentInvalid UTF-8 in username for uid %d. SkippingError looking up user name for uid %dUnsupporting identity of GType %s[gnome-shell] handle_cancelled_in_idleAuthentication dialog was dismissed by the usershell_screenshot_composite_to_streamThe PID of the icon's applicationBackground color (only if we don't have transparency)failed to get /proc/self/cmdline: %s[gnome-shell] run_leisure_functionsRGBA = ADD (SRC_COLOR * (SRC_COLOR[A]), DST_COLOR * (1-SRC_COLOR[A]))this prompt can only show one prompt at a timeshell_keyring_prompt_password_asyncthis prompt is already promptingshell_keyring_prompt_confirm_asyncSHELL_IS_KEYRING_PROMPT (self)[gnome-shell] statistics_timeoutperf_log->events->len == EVENT_STATISTICS_COLLECTED + 1perf_log->events->len == EVENT_SET_TIME + 1Finished collecting statisticsfailed to resolve required GL symbol "%s"
clutter.paintCompletedTimestampOnly supported statistic signatures are 'i' and 'x'
Unsupported signature in event{ "name": "%s",
    "description": "%s"PolKit failed to properly get our sessionagent->current_request != NULLSHELL_IS_POLKIT_AUTHENTICATION_AGENT (agent)Only one screenshot operation at a time is permittedshell_screenshot_screenshot_areaSHELL_IS_SCREENSHOT (screenshot)shell_screenshot_screenshot_stage_to_contentg_async_result_is_tagged (result, shell_screenshot_screenshot)g_async_result_is_tagged (result, shell_screenshot_screenshot_stage_to_content)g_async_result_is_tagged (result, shell_screenshot_screenshot_area)g_async_result_is_tagged (result, shell_screenshot_screenshot_window)cairo_image_surface_get_format (priv->image) == CAIRO_FORMAT_ARGB32g_async_result_is_tagged (result, shell_screenshot_pick_color)g_async_result_is_tagged (result, shell_screenshot_composite_to_stream)password_actor == NULL || CLUTTER_IS_TEXT (password_actor)confirm_actor == NULL || CLUTTER_IS_TEXT (confirm_actor)shell tray: plug window is goneevent_type == CLUTTER_BUTTON_RELEASE || event_type == CLUTTER_KEY_PRESS || event_type == CLUTTER_KEY_RELEASEEnd of frame, possibly including swap timeshell_screenshot_screenshot_windowH���� ��8��P��`�����H������������������������(��8�����t����������������,��t�������������$��T��shell_tray_icon_clickshell_tray_icon_newshell_tray_icon_constructedshell_screenshot_composite_to_stream_finishshell_screenshot_pick_color_finishshell_screenshot_pick_colorshell_screenshot_screenshot_window_finishshell_screenshot_screenshot_windowshell_screenshot_screenshot_area_finishshell_screenshot_screenshot_areashell_screenshot_screenshot_stage_to_content_finishshell_screenshot_screenshot_stage_to_contentshell_screenshot_screenshot_finishshell_screenshot_screenshotwrite_screenshot_threadshell_polkit_authentication_agent_completereplay_to_jsonshell_perf_log_initshell_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_actorshell_keyring_prompt_confirm_finishshell_keyring_prompt_password_finishshell_keyring_prompt_disposeremove_mnemonicsshell_gtk_embed_newshell_glsl_effect_add_glsl_snippetshell_global_set_debug_flagsshell_global_get_debug_flagsreplace_contents_asyncshell_global_get_session_modeshell_global_end_work_shell_global_set_pluginshell_global_get_window_actors�?�?$@�o@child != NULLshell_util_touch_file_asyncG_IS_FILE (file)fd %d is not CLOEXECdata->job == NULL../src/shell-util.c(o)(u&o&s&s)doneNot systemd managed/org/freedesktop/systemd1JobRemovedorg.freedesktop.systemd1window-containerShellWindowPreviewShellWindowTrackerShellWMShellAppCacheNaTrayManagerBounding Boxbounding-boxShellWindowPreviewLayoutunminimizesize-changedsize-changekill-switch-workspacekill-window-effectsshow-tile-previewhide-tile-previewshow-window-menufilter-keybindingconfirm-display-changecreate-close-dialognew-requestcancel-requestShellNetworkAgentCanceled by NetworkManager../src/shell-network-agent.cconnection-uuids_conshell_app_cache_do_updatesetting-keya{sa{sv}}{s@a{sv}}No plugin for %sconnection_uuid != NULLNetwork secret for %s/%s/%ssetting-nameattrssetting_key != NULLconnection_id != NULLs_con != NULLsetting_nameVPN %s secret for %s/%s/vpnservice_namesettingsecretsNameDesktop Entrypath != NULL../src/shell-app-cache.cfolders != NULLdesktop-directoriesNaTrayChildorientationtray_icon_addedtray_icon_removedmessage_sentmessage_cancelledlost_selectionGDK_IS_WINDOW (window)../src/tray/na-tray-manager.cnotification_areaGTK_IS_INVISIBLE (invisible)_NET_SYSTEM_TRAY_ORIENTATIONmanager->invisible != NULL_NET_SYSTEM_TRAY_COLORSshell-stop-pickLC_TIMEG_IS_TASK (res)target_scale > 0.0fn_captures > 0StartUnitStopUnit(s)GetUnitREADY=1../src/shell-window-preview.cposition-changedMETA_IS_WINDOW (window)window_info != NULL../src/shell-window-tracker.ca{ss}setting != NULLservice != NULLSHELL_IS_NETWORK_AGENT (self)request != NULLuser_data == NULLSHELL_IS_APP_CACHE (cache)G_IS_TASK (task)SHELL_IS_APP_CACHE (self)icon_window != NoneGDK_IS_SCREEN (screen)plug_removedNA_IS_TRAY_CHILD (child)UTF8_STRING_NET_WM_NAMEMANAGER_NET_SYSTEM_TRAY_OPCODE_NET_SYSTEM_TRAY_MESSAGE_DATA_NET_SYSTEM_TRAY_VISUALmanager->screen == NULL_NET_SYSTEM_TRAY_S%dNA_IS_TRAY_MANAGER (manager)Unknown statistic '%s'
/proc/self/fdscale-factorrealizeplug-addedFocused applicationFocus Appstartup-sequence-changedtracked-windows-changed_chromium/snap/bin/chromium/snap/bin/chromium_notify::wm-classnotify::gtk-application-idunmanaged%s.Failed to take screenshot: %sCould not issue '%s' systemd callSystemd job completed with status "%s"Error fetching own systemd unit: %sorg.freedesktop.systemd1.Managercreate-inhibit-shortcuts-dialogInternal error while retrieving secrets from the keyring (%s)The request could not be completed.  Keyring result: %sgtk_widget_get_realized (invisible)Unknown value of _NL_TIME_WEEK_1STDAY.
File %s contains invalid UTF-8../src/shell-window-preview-layout.cSHELL_IS_WINDOW_PREVIEW_LAYOUT (self)The secret agent is going awayshell_network_agent_search_vpn_pluginNetwork dialog was canceled by the userAn internal error occurred while processing the request.SHELL_IS_APP_CACHE (source_object)Statistic '%s'; defined with signature '%s', used with '%s'
Event '%s'; defined with signature '%s', used with '%s'
Discarding unknown event '%s'
Open fd CLOEXEC check completeorg.freedesktop.NetworkManager.Connectionget_app_from_idna_tray_manager_get_orientationna_tray_manager_set_colorsna_tray_manager_set_orientationna_tray_manager_manage_screenna_tray_manager_set_visual_propertyna_tray_manager_manage_screen_x11na_tray_manager_set_colors_propertyna_tray_manager_set_orientation_propertyna_tray_manager_unmanagena_tray_child_get_wm_classna_tray_child_has_alphana_tray_child_get_titlena_tray_child_newshell_app_cache_translate_foldershell_app_cache_get_infoshell_app_cache_get_allmonitor_desktop_directories_for_data_dirshell_app_cache_queue_updateapply_update_cbshell_app_cache_workerload_foldersload_foldershell_network_agent_delete_secretsvpn_secret_iter_cbcreate_keyring_add_attr_listsave_one_secretshell_network_agent_search_vpn_plugin_finishshell_network_agent_search_vpn_pluginshell_network_agent_respondshell_network_agent_set_passwordshell_network_agent_add_vpn_secretis_connection_always_askshell_window_preview_layout_get_windowsshell_window_preview_layout_remove_windowshell_window_preview_layout_add_windowon_actor_destroyedon_systemd_call_cbshell_util_composite_capture_imagesshell_util_touch_file_finishshell_util_touch_file_asyncna_tray_icon_removedGVariantl(�



 ""$%'((((*++++,,./002357888:::;;;=>>>>>>>???BBDEHIKNPPQRUVXXXXXY[__aacccegghiijkmqrsssttvwyz{{~��������������������|��3$l
v�@4�ܯ?$@4vP4�M# X�$�MvNClKz�$Clv`lw��n$w�v����]-�$��v�^ú]$^vhE��$v4��/X�p4�vH���N$v0��r_�
*��L��(��_˾+(�v0����O��
��v�����X�p��v��"_��_$"_
v0_u��k$$u�v����Ե������L����/�:�
��v���3�B6n�v�&�5��f
&�
v0�޷tdwn޷
v���g>Ln
��v������V�$��v�b�'5s$b�	vp�s���Xdns�v��1��!T$1�v@�*�9��$*
v(*jDD#d$jDvxD	�KP�	�L��^�$�v0�P��*��$P�vX�,�],�$,v(,�m��	�$�mvn'~;�
�$'~v@~j���f
j�v���[��G$�v0���D԰�*��L����s�
*�L���P[�+�
v�@��FA$@vX'*iI�1
'*v@*�=���+�=	v�='D7,p�C'DL0D@D^��t*@DLDDhD>��X$hDv�D�S�X��$�S
v�S�x���$�xv�xI�7��I
I�vX��hQ�$�v�s��\�$s�v��D�*�pE$D�	vP��x	�XH($�x	v�x	n�	��!�$n�	
v��	T
���$T
	v`
�x
���$�x
v�x
c�
��/�$c�
vx�
a�-�
avaU��=i�$U�vh���"J�$��v���B��N$�v(�d�s�?nd�vx����Á$�v0�[

��i{p[

vp

�J
�f$�J
vK
�W
���$�W
v�W
��
�Ҹ!$��
v��
�"�%�$�"v#�E��$0�EL�E�E�T"
�E
vF�dzm�$�dv�d%����$%�
v8�M�s�ۗ
M�	vX����*?$��v��qJu�
qv�<���V+<v(<lB���x$lBv�B��jXon�
v��
g���+�
v�
�����3
��v���U�<�$�v8���q[0�$��v���G��S
�Gv�GKo�W�.$Kov`o���f-$��v��q�Fq�nqv� <�e�Cn <v0<�R��
�
�Rv�R�f~a�$�fvg�9�4$�9
v�9�Rڸ}n�R	v�R�5�$�v ����uk$��
v��u `[��$u v� �0�q�Z$�0v�0 L8���$ Lv0L�c��%�c	v�c�����r$��v�����J�$��vЩ���"n��v��l���	_pl�vx���)�k�$��v���HTS�.$�Hv�H��`B�2%��v��[�?k�$[�	vh��G֐n�	v��7�+�v��3�5�,$�3v�3v?-�9nv?v�?i�fN�$ivi-��o�$-�v@�Y�x�($YL`�\ow�$�v��%�,[L$�%L�%�%����+�%	v�%h2��k�ph2
vx2R/}�
R
vR^�h��$^v�^m�j�ے$m�v��ؿӁt$ؿv���-,�$��	v��#��t0
#�v8���
��;p��v��O��z$�v ��U���
�Uv�U�d딏3p�dve߉���
߉v��=�z$�	v(�h����nh�vp������
��v��X%`�+	nX%
vh%�0gC
�0
v�0>5��!�
>5	vH5H9
�7�$H9
vX9��W��$��v��nZ��$�
v(�O�[F�$O�vh�	��5�n	�	v� 08�$ v  �b Xᩦ$�b v�b �!'��%�!v�!�+!��E�
�+!
v�+!�T!�X�+�T!v�T!��!Ix�$��!v��!W�"�sp8$W�"
vh�"�"��n+�"v�"��"$��$��"v�"W�"�}�$W�"vh�"ż"���nż"
vм"f�#��>�$f�#vx�#q$windowMenu.js�#// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*
/* exported WindowMenuManager */

const { GLib, Meta, St } = imports.gi;

const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PopupMenu = imports.ui.popupMenu;
const Screenshot = imports.ui.screenshot;

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;

        // Translators: entry in the window right click menu.
        item = this.addAction(_('Take Screenshot'), async () => {
            try {
                const actor = window.get_compositor_private();
                const content = actor.paint_to_content(null);
                const texture = content.get_texture();

                await Screenshot.captureScreenshot(texture, null, 1, null);
            } catch (e) {
                logError(e, 'Error capturing screenshot');
            }
        });

        item = this.addAction(_('Hide'), () => {
            window.minimize();
        });
        if (!window.can_minimize())
            item.setSensitive(false);

        if (window.get_maximized()) {
            item = this.addAction(_('Restore'), () => {
                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 => {
            this._grabAction(window, Meta.GrabOp.KEYBOARD_MOVING, event.get_time());
        });
        if (!window.allows_move())
            item.setSensitive(false);

        item = this.addAction(_("Resize"), event => {
            this._grabAction(window, Meta.GrabOp.KEYBOARD_RESIZING_UNKNOWN, 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 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);
    }

    _grabAction(window, grabOp, time) {
        if (global.display.get_grab_op() == Meta.GrabOp.NONE) {
            window.begin_grab_op(grabOp, true, time);
            return;
        }

        let waitId = 0;
        let id = global.display.connect('grab-op-end', display => {
            display.disconnect(id);
            GLib.source_remove(waitId);

            window.begin_grab_op(grabOp, true, time);
        });

        waitId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
            global.display.disconnect(id);
            return GLib.SOURCE_REMOVE;
        });
    }
};

var WindowMenuManager = class {
    constructor() {
        this._manager = new PopupMenu.PopupMenuManager(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 (!Main.sessionMode.hasWmMenus)
            return;

        if (type != Meta.WindowMenuType.WM)
            throw new Error('Unsupported window menu type');
        let menu = new WindowMenu(window, this._sourceActor);

        this._manager.addMenu(menu);

        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.FADE);
        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)closeDialog.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CloseDialog */

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

const Dialog = imports.ui.dialog;
const Main = imports.ui.main;

var FROZEN_WINDOW_BRIGHTNESS = -0.3;
var DIALOG_TRANSITION_TIME = 150;
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;
    }

    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 description = _('You may choose to wait a short while for it to ' +
                            'continue or force the application to quit entirely.');
        return new Dialog.MessageDialogContent({ title, description });
    }

    _updateScale() {
        // Since this is a child of MetaWindowActor (which, for Wayland clients,
        // applies the geometry scale factor to its children itself, see
        // meta_window_actor_set_geometry_scale()), make sure we don't apply
        // the factor twice in the end.
        if (this._window.get_client_type() !== Meta.WindowClientType.WAYLAND)
            return;

        let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        this._dialog.set_scale(1 / scaleFactor, 1 / scaleFactor);
    }

    _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.contentLayout.add_child(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.KEY_Escape,
        });

        global.focus_manager.add_group(this._dialog);

        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        themeContext.connect('notify::scale-factor', this._updateScale.bind(this));

        this._updateScale();
    }

    _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;
            });

        global.display.connectObject(
            'notify::focus-window', this._onFocusChanged.bind(this), this);

        global.stage.connectObject(
            'notify::key-focus', this._onFocusChanged.bind(this), this);

        this._addWindowEffect();
        this._initDialog();

        this._dialog._dialog.scale_y = 0;
        this._dialog._dialog.set_pivot_point(0.5, 0.5);

        this._dialog._dialog.ease({
            scale_y: 1,
            mode: Clutter.AnimationMode.LINEAR,
            duration: 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.disconnectObject(this);
        global.stage.disconnectObject(this);

        this._dialog._dialog.remove_all_transitions();

        let dialog = this._dialog;
        this._dialog = null;
        this._removeWindowEffect();

        dialog.makeInactive();
        dialog._dialog.ease({
            scale_y: 0,
            mode: Clutter.AnimationMode.LINEAR,
            duration: DIALOG_TRANSITION_TIME,
            onComplete: () => dialog.destroy(),
        });
    }

    vfunc_focus() {
        if (this._dialog)
            this._dialog.grab_key_focus();
    }
});
(uuay)appFavorites.js+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getAppFavorites */

const Shell = imports.gi.Shell;
const ParentalControlsManager = imports.misc.parentalControlsManager;
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',
    'eog.desktop': 'org.gnome.eog.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',
    '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',
    'seahorse.desktop': 'org.gnome.seahorse.Application.desktop',
    'shotwell.desktop': 'org.gnome.Shotwell.desktop',
    'tali.desktop': 'org.gnome.Tali.desktop',
    'totem.desktop': 'org.gnome.Totem.desktop',
    'evince.desktop': 'org.gnome.Evince.desktop',
};

class AppFavorites {
    constructor() {
        // Filter the apps through the user’s parental controls.
        this._parentalControlsManager = ParentalControlsManager.getDefault();
        this._parentalControlsManager.connect('app-filter-changed', () => {
            this.reload();
            this.emit('changed');
        });

        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._parentalControlsManager.shouldShowApp(app.app_info));
        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;

        if (!this._parentalControlsManager.shouldShowApp(app.app_info))
            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);

        let msg;
        if (imports.misc.desktop.is('ubuntu'))
            msg = _('%s has been added to your favorites.').format(app.get_name());
        else
            msg = _('%s has been pinned to the dash.').format(app.get_name());
        Main.overview.setMessage(msg, {
            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;

        let msg;
        if (imports.misc.desktop.is('ubuntu'))
            msg = _('%s has been removed from your favorites.').format(app.get_name());
        else
            msg = _('%s has been unpinned from the dash.').format(app.get_name());
        Main.overview.setMessage(msg, {
            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)shellMountOperation.jsf// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ShellMountOperation, GnomeShellMountOpHandler */

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

const Animation = imports.ui.animation;
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;
const Util = imports.misc.util;

var LIST_ITEM_ICON_SIZE = 48;
var WORK_SPINNER_ICON_SIZE = 16;

const REMEMBER_MOUNT_PASSWORD_KEY = 'remember-mount-password';

/* ------ Common Utils ------- */
function _setButtonsForChoices(dialog, oldChoices, choices) {
    let buttons = [];
    let buttonsChanged = oldChoices.length !== choices.length;

    for (let idx = 0; idx < choices.length; idx++) {
        let button = idx;

        buttonsChanged ||= oldChoices[idx] !== choices[idx];

        buttons.unshift({
            label: choices[idx],
            action: () => dialog.emit('response', button),
        });
    }

    if (buttonsChanged)
        dialog.setButtons(buttons);
}

function _setLabelsForMessage(content, message) {
    let labels = message.split('\n');

    content.title = labels.shift();
    content.description = labels.join('\n');
}

/* -------------------------------------------------------- */

var ShellMountOperation = class {
    constructor(source, params) {
        params = Params.parse(params, { existingDialog: null });

        this._dialog = null;
        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));
    }

    _closeExistingDialog() {
        if (!this._existingDialog)
            return;

        this._existingDialog.close();
        this._existingDialog = null;
    }

    _onAskQuestion(op, message, choices) {
        this._closeExistingDialog();
        this._dialog = new ShellMountQuestionDialog();

        this._dialog.connectObject('response',
            (object, choice) => {
                this.mountOp.set_choice(choice);
                this.mountOp.reply(Gio.MountOperationResult.HANDLED);

                this.close();
            }, this);

        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, flags);
        }

        this._dialog.connectObject('response',
            (object, choice, password, remember, hiddenVolume, systemVolume, pim) => {
                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.set_is_tcrypt_hidden_volume(hiddenVolume);
                    this.mountOp.set_is_tcrypt_system_volume(systemVolume);
                    this.mountOp.set_pim(pim);
                    this.mountOp.reply(Gio.MountOperationResult.HANDLED);
                }
            }, this);
        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._dialog = this._processesDialog;

            this._processesDialog.connectObject('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);
            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() {
        this._dialog?.disconnectObject(this);
        return this._dialog;
    }
};

var ShellUnmountNotifier = GObject.registerClass(
class ShellUnmountNotifier extends MessageTray.Source {
    _init() {
        super._init('', '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.showNotification(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.showNotification(notification);
        }
    }
});

var ShellMountQuestionDialog = GObject.registerClass({
    Signals: { 'response': { param_types: [GObject.TYPE_INT] } },
}, class ShellMountQuestionDialog extends ModalDialog.ModalDialog {
    _init() {
        super._init({ styleClass: 'mount-question-dialog' });

        this._oldChoices = [];

        this._content = new Dialog.MessageDialogContent();
        this.contentLayout.add_child(this._content);
    }

    vfunc_key_release_event(event) {
        if (event.keyval === Clutter.KEY_Escape) {
            this.emit('response', -1);
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    update(message, choices) {
        _setLabelsForMessage(this._content, message);
        _setButtonsForChoices(this, this._oldChoices, choices);
        this._oldChoices = choices;
    }
});

var ShellMountPasswordDialog = GObject.registerClass({
    Signals: {
        'response': {
            param_types: [
                GObject.TYPE_INT,
                GObject.TYPE_STRING,
                GObject.TYPE_BOOLEAN,
                GObject.TYPE_BOOLEAN,
                GObject.TYPE_BOOLEAN,
                GObject.TYPE_UINT,
            ],
        },
    },
}, class ShellMountPasswordDialog extends ModalDialog.ModalDialog {
    _init(message, flags) {
        let strings = message.split('\n');
        let title = strings.shift() || null;
        let description = strings.shift() || null;
        super._init({ styleClass: 'prompt-dialog' });

        let disksApp = Shell.AppSystem.get_default().lookup_app('org.gnome.DiskUtility.desktop');

        let content = new Dialog.MessageDialogContent({ title, description });

        let passwordGridLayout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
        let passwordGrid = new St.Widget({
            style_class: 'prompt-dialog-password-grid',
            layout_manager: passwordGridLayout,
        });
        passwordGridLayout.hookup_style(passwordGrid);

        let rtl = passwordGrid.get_text_direction() === Clutter.TextDirection.RTL;
        let curGridRow = 0;

        if (flags & Gio.AskPasswordFlags.TCRYPT) {
            this._hiddenVolume = new CheckBox.CheckBox(_("Hidden Volume"));
            content.add_child(this._hiddenVolume);

            this._systemVolume = new CheckBox.CheckBox(_("Windows System Volume"));
            content.add_child(this._systemVolume);

            this._keyfilesCheckbox = new CheckBox.CheckBox(_("Uses Keyfiles"));
            this._keyfilesCheckbox.connect("clicked", this._onKeyfilesCheckboxClicked.bind(this));
            content.add_child(this._keyfilesCheckbox);

            this._keyfilesLabel = new St.Label({ visible: false });
            this._keyfilesLabel.clutter_text.set_markup(
                /* Translators: %s is the Disks application */
                _('To unlock a volume that uses keyfiles, use the <i>%s</i> utility instead.')
               .format(disksApp.get_name()));
            this._keyfilesLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
            this._keyfilesLabel.clutter_text.line_wrap = true;
            content.add_child(this._keyfilesLabel);

            this._pimEntry = new St.PasswordEntry({
                style_class: 'prompt-dialog-password-entry',
                hint_text: _('PIM Number'),
                can_focus: true,
                x_expand: true,
            });
            this._pimEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
            ShellEntry.addContextMenu(this._pimEntry);

            if (rtl)
                passwordGridLayout.attach(this._pimEntry, 1, curGridRow, 1, 1);
            else
                passwordGridLayout.attach(this._pimEntry, 0, curGridRow, 1, 1);
            curGridRow += 1;
        } else {
            this._hiddenVolume = null;
            this._systemVolume = null;
            this._pimEntry = null;
        }

        this._passwordEntry = new St.PasswordEntry({
            style_class: 'prompt-dialog-password-entry',
            hint_text: _('Password'),
            can_focus: true,
            x_expand: true,
        });
        this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
        this.setInitialKeyFocus(this._passwordEntry);
        ShellEntry.addContextMenu(this._passwordEntry);

        this._workSpinner = new Animation.Spinner(WORK_SPINNER_ICON_SIZE, {
            animate: true,
        });

        if (rtl) {
            passwordGridLayout.attach(this._workSpinner, 0, curGridRow, 1, 1);
            passwordGridLayout.attach(this._passwordEntry, 1, curGridRow, 1, 1);
        } else {
            passwordGridLayout.attach(this._passwordEntry, 0, curGridRow, 1, 1);
            passwordGridLayout.attach(this._workSpinner, 1, curGridRow, 1, 1);
        }
        curGridRow += 1;

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

        let capsLockWarning = new ShellEntry.CapsLockWarning();
        warningBox.add_child(capsLockWarning);

        this._errorMessageLabel = new St.Label({
            style_class: 'prompt-dialog-error-label',
            opacity: 0,
        });
        this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._errorMessageLabel.clutter_text.line_wrap = true;
        warningBox.add_child(this._errorMessageLabel);

        passwordGridLayout.attach(warningBox, 0, curGridRow, 2, 1);

        content.add_child(passwordGrid);

        if (flags & Gio.AskPasswordFlags.SAVING_SUPPORTED) {
            this._rememberChoice = new CheckBox.CheckBox(_("Remember Password"));
            this._rememberChoice.checked =
                global.settings.get_boolean(REMEMBER_MOUNT_PASSWORD_KEY);
            content.add_child(this._rememberChoice);
        } else {
            this._rememberChoice = null;
        }

        this.contentLayout.add_child(content);

        this._defaultButtons = [{
            label: _("Cancel"),
            action: this._onCancelButton.bind(this),
            key: Clutter.KEY_Escape,
        }, {
            label: _("Unlock"),
            action: this._onUnlockButton.bind(this),
            default: true,
        }];

        this._usesKeyfilesButtons = [{
            label: _("Cancel"),
            action: this._onCancelButton.bind(this),
            key: Clutter.KEY_Escape,
        }, {
            /* Translators: %s is the Disks application */
            label: _("Open %s").format(disksApp.get_name()),
            action: this._onOpenDisksButton.bind(this),
            default: true,
        }];

        this.setButtons(this._defaultButtons);
    }

    reaskPassword() {
        this._workSpinner.stop();
        this._passwordEntry.set_text('');
        this._errorMessageLabel.text = _('Sorry, that didn’t work. Please try again.');
        this._errorMessageLabel.opacity = 255;

        Util.wiggle(this._passwordEntry);
    }

    _onCancelButton() {
        this.emit('response', -1, '', false, false, false, 0);
    }

    _onUnlockButton() {
        this._onEntryActivate();
    }

    _onEntryActivate() {
        let pim = 0;
        if (this._pimEntry !== null) {
            pim = this._pimEntry.get_text();

            if (isNaN(pim)) {
                this._pimEntry.set_text('');
                this._errorMessageLabel.text = _('The PIM must be a number or empty.');
                this._errorMessageLabel.opacity = 255;
                return;
            }

            this._errorMessageLabel.opacity = 0;
        }

        global.settings.set_boolean(REMEMBER_MOUNT_PASSWORD_KEY,
            this._rememberChoice && this._rememberChoice.checked);

        this._workSpinner.play();
        this.emit('response', 1,
            this._passwordEntry.get_text(),
            this._rememberChoice &&
            this._rememberChoice.checked,
            this._hiddenVolume &&
            this._hiddenVolume.checked,
            this._systemVolume &&
            this._systemVolume.checked,
            parseInt(pim));
    }

    _onKeyfilesCheckboxClicked() {
        let useKeyfiles = this._keyfilesCheckbox.checked;
        this._passwordEntry.reactive = !useKeyfiles;
        this._passwordEntry.can_focus = !useKeyfiles;
        this._pimEntry.reactive = !useKeyfiles;
        this._pimEntry.can_focus = !useKeyfiles;
        this._rememberChoice.reactive = !useKeyfiles;
        this._rememberChoice.can_focus = !useKeyfiles;
        this._keyfilesLabel.visible = useKeyfiles;
        this.setButtons(useKeyfiles ? this._usesKeyfilesButtons : this._defaultButtons);
    }

    _onOpenDisksButton() {
        let app = Shell.AppSystem.get_default().lookup_app('org.gnome.DiskUtility.desktop');
        if (app) {
            app.activate();
        } else {
            Main.notifyError(
                /* Translators: %s is the Disks application */
                _("Unable to start %s").format(app.get_name()),
                /* Translators: %s is the Disks application */
                _('Couldn’t find the %s application').format(app.get_name()));
        }
        this._onCancelButton();
    }
});

var ShellProcessesDialog = GObject.registerClass({
    Signals: { 'response': { param_types: [GObject.TYPE_INT] } },
}, class ShellProcessesDialog extends ModalDialog.ModalDialog {
    _init() {
        super._init({ styleClass: 'processes-dialog' });

        this._oldChoices = [];

        this._content = new Dialog.MessageDialogContent();
        this.contentLayout.add_child(this._content);

        this._applicationSection = new Dialog.ListSection();
        this._applicationSection.hide();
        this.contentLayout.add_child(this._applicationSection);
    }

    vfunc_key_release_event(event) {
        if (event.keyval === Clutter.KEY_Escape) {
            this.emit('response', -1);
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _setAppsForPids(pids) {
        // remove all the items
        this._applicationSection.list.destroy_all_children();

        pids.forEach(pid => {
            let tracker = Shell.WindowTracker.get_default();
            let app = tracker.get_app_from_pid(pid);

            if (!app)
                return;

            let listItem = new Dialog.ListSectionItem({
                icon_actor: app.create_icon_texture(LIST_ITEM_ICON_SIZE),
                title: app.get_name(),
            });
            this._applicationSection.list.add_child(listItem);
        });

        this._applicationSection.visible =
            this._applicationSection.list.get_n_children() > 0;
    }

    update(message, processes, choices) {
        this._setAppsForPids(processes);
        _setLabelsForMessage(this._content, message);
        _setButtonsForChoices(this, this._oldChoices, choices);
        this._oldChoices = choices;
    }
});

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._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;
        }
    }

    /**
     * AskPassword:
     * @param {Array} params
     *   {string} id: an opaque ID identifying the object for which
     *       the operation is requested
     *   {string} message: the message to display
     *   {string} icon_name: the name of an icon to display
     *   {string} default_user: the default username for display
     *   {string} default_domain: the default domain for display
     *   {Gio.AskPasswordFlags} flags: a set of GAskPasswordFlags
     *   {Gio.MountOperationResults} response: a GMountOperationResult
     *   {Object} response_details: a dictionary containing 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
     * @param {Gio.DBusMethodInvocation} invocation
     *      The ID must be unique in the context of the calling process.
     *
     * 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, flags);
        this._dialog.connect('response',
            (object, choice, password, remember, hiddenVolume, systemVolume, pim) => {
                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);
                    details['hidden_volume'] = GLib.Variant.new('b', hiddenVolume);
                    details['system_volume'] = GLib.Variant.new('b', systemVolume);
                    details['pim'] = GLib.Variant.new('u', pim);
                }

                this._clearCurrentRequest(response, details);
            });
        this._dialog.open();
    }

    /**
     * AskQuestion:
     * @param {Array} params - params
     *   {string} 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.
     *   {string} message: the message to display
     *   {string} icon_name: the name of an icon to display
     *   {string[]} choices: an array of choice strings
     * @param {Gio.DBusMethodInvocation} invocation - invocation
     *
     * 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(message);
        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, choices);
        this._dialog.open();
    }

    /**
     * ShowProcesses:
     * @param {Array} params - params
     *   {string} 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.
     *   {string} message: the message to display
     *   {string} icon_name: the name of an icon to display
     *   {number[]} application_pids: the PIDs of the applications to display
     *   {string[]} choices: an array of choice strings
     * @param {Gio.DBusMethodInvocation} invocation - invocation
     *
     * 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._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:
     * @param {Array} _params - params
     * @param {Gio.DBusMethodInvocation} _invocation - invocation
     *
     * 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)animation.jsd// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Animation, AnimatedIcon, Spinner */

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

const Params = imports.misc.params;

var ANIMATED_ICON_UPDATE_TIMEOUT = 16;
var SPINNER_ANIMATION_TIME = 300;
var SPINNER_ANIMATION_DELAY = 1000;

var Animation = GObject.registerClass(
class Animation extends St.Bin {
    _init(file, width, height, speed) {
        const themeContext = St.ThemeContext.get_for_stage(global.stage);

        super._init({
            style: `width: ${width}px; height: ${height}px;`,
        });

        this.connect('destroy', this._onDestroy.bind(this));
        this.connect('resource-scale-changed',
            this._loadFile.bind(this, file, width, height));

        themeContext.connectObject('notify::scale-factor',
            () => {
                this._loadFile(file, width, height);
                this.set_size(width * themeContext.scale_factor, height * themeContext.scale_factor);
            }, this);

        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) {
            GLib.source_remove(this._timeoutId);
            this._timeoutId = 0;
        }

        this._isPlaying = false;
    }

    _loadFile(file, width, height) {
        const resourceScale = this.get_resource_scale();
        let wasPlaying = this._isPlaying;

        if (this._isPlaying)
            this.stop();

        this._isLoaded = false;
        this.destroy_all_children();

        let textureCache = St.TextureCache.get_default();
        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        this._animations = textureCache.load_sliced_image(file, width, height,
                                                          scaleFactor, resourceScale,
                                                          this._animationsLoaded.bind(this));
        this._animations.set({
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.set_child(this._animations);

        if (wasPlaying)
            this.play();
    }

    _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.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();
    }
});

var AnimatedIcon = GObject.registerClass(
class AnimatedIcon extends Animation {
    _init(file, size) {
        super._init(file, size, size, ANIMATED_ICON_UPDATE_TIMEOUT);
    }
});

var Spinner = GObject.registerClass(
class Spinner extends AnimatedIcon {
    _init(size, params) {
        params = Params.parse(params, {
            animate: false,
            hideOnStop: false,
        });
        let file = Gio.File.new_for_uri('resource:///org/gnome/shell/theme/process-working.svg');
        super._init(file, size);

        this.opacity = 0;
        this._animate = params.animate;
        this._hideOnStop = params.hideOnStop;
        this.visible = !this._hideOnStop;
    }

    _onDestroy() {
        this._animate = false;
        super._onDestroy();
    }

    play() {
        this.remove_all_transitions();
        this.show();

        if (this._animate) {
            super.play();
            this.ease({
                opacity: 255,
                delay: SPINNER_ANIMATION_DELAY,
                duration: SPINNER_ANIMATION_TIME,
                mode: Clutter.AnimationMode.LINEAR,
            });
        } else {
            this.opacity = 255;
            super.play();
        }
    }

    stop() {
        this.remove_all_transitions();

        if (this._animate) {
            this.ease({
                opacity: 0,
                duration: SPINNER_ANIMATION_TIME,
                mode: Clutter.AnimationMode.LINEAR,
                onComplete: () => {
                    super.stop();
                    if (this._hideOnStop)
                        this.hide();
                },
            });
        } else {
            this.opacity = 0;
            super.stop();

            if (this._hideOnStop)
                this.hide();
        }
    }
});
(uuay)ibusCandidatePopup.js61// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CandidatePopup */

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

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 = GObject.registerClass({
    Signals: {
        'candidate-clicked': {
            param_types: [
                GObject.TYPE_UINT, GObject.TYPE_UINT, Clutter.ModifierType.$gtype,
            ],
        },
        'cursor-down': {},
        'cursor-up': {},
        'next-page': {},
        'previous-page': {},
    },
}, class CandidateArea extends St.BoxLayout {
    _init() {
        super._init({
            vertical: true,
            reactive: true,
            visible: false,
        });
        this._candidateBoxes = [];
        for (let i = 0; i < MAX_CANDIDATES_PER_PAGE; ++i) {
            const 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_child(box._indexLabel);
            box.add_child(box._candidateLabel);
            this._candidateBoxes.push(box);
            this.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._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',
            x_expand: true,
        });
        this._previousButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
        this._buttonBox.add_child(this._previousButton);

        this._nextButton = new St.Button({
            style_class: 'candidate-page-button candidate-page-button-next button',
            x_expand: true,
        });
        this._nextButton.child = new St.Icon({ style_class: 'candidate-page-button-icon' });
        this._buttonBox.add_child(this._nextButton);

        this.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;
    }

    vfunc_scroll_event(scrollEvent) {
        switch (scrollEvent.direction) {
        case Clutter.ScrollDirection.UP:
            this.emit('cursor-up');
            break;
        case Clutter.ScrollDirection.DOWN:
            this.emit('cursor-down');
            break;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    setOrientation(orientation) {
        if (this._orientation == orientation)
            return;

        this._orientation = orientation;

        if (this._orientation == IBus.Orientation.HORIZONTAL) {
            this.vertical = false;
            this.remove_style_class_name('vertical');
            this.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.vertical = true;
            this.add_style_class_name('vertical');
            this.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;
    }
});

var CandidatePopup = GObject.registerClass(
class IbusCandidatePopup extends BoxPointer.BoxPointer {
    _init() {
        super._init(St.Side.TOP);
        this.visible = false;
        this.style_class = 'candidate-popup-boxpointer';

        this._dummyCursor = new Clutter.Actor({ opacity: 0 });
        Main.layoutManager.uiGroup.add_actor(this._dummyCursor);

        Main.layoutManager.addTopChrome(this);

        const box = new St.BoxLayout({
            style_class: 'candidate-popup-content',
            vertical: true,
        });
        this.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);

        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', () => {
            this._preeditText.show();
            this._updateVisibility();
        });
        panelService.connect('hide-preedit-text', () => {
            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', () => {
            this._auxText.show();
            this._updateVisibility();
        });
        panelService.connect('hide-auxiliary-text', () => {
            this._auxText.hide();
            this._updateVisibility();
        });
        panelService.connect('update-lookup-table', (_ps, lookupTable, visible) => {
            this._candidateArea.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();
            Main.keyboard.setSuggestionsVisible(visible);

            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', () => {
            Main.keyboard.setSuggestionsVisible(true);
            this._candidateArea.show();
            this._updateVisibility();
        });
        panelService.connect('hide-lookup-table', () => {
            Main.keyboard.setSuggestionsVisible(false);
            this._candidateArea.hide();
            this._updateVisibility();
        });
        panelService.connect('focus-out', () => {
            this.close(BoxPointer.PopupAnimation.NONE);
            Main.keyboard.resetSuggestions();
        });
    }

    _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));

        if (this.visible)
            this.setPosition(this._dummyCursor, 0);
    }

    _updateVisibility() {
        let isVisible = !Main.keyboard.visible &&
                         (this._preeditText.visible ||
                          this._auxText.visible ||
                          this._candidateArea.visible);

        if (isVisible) {
            this.setPosition(this._dummyCursor, 0);
            this.open(BoxPointer.PopupAnimation.NONE);
            // We shouldn't be above some components like the screenshot UI,
            // so don't raise to the top.
            // The on-screen keyboard is expected to be above any entries,
            // so just above the keyboard gets us to the right layer.
            const { keyboardBox } = Main.layoutManager;
            this.get_parent().set_child_above_sibling(this, keyboardBox);
        } else {
            this.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)init.js�import { setConsoleLogDomain } from 'console';

setConsoleLogDomain('GNOME Shell');

imports.ui.environment.init();
imports.ui.main.start();
(uuay)dnd.js|// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported addDragMonitor, removeDragMonitor, makeDraggable */

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

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

// Time to scale down to maxDragActorSize
var SCALE_ANIMATION_TIME = 250;
// Time to animate to original position on cancel
var SNAP_BACK_ANIMATION_TIME = 250;
// Time to animate to original position on success
var REVERT_ANIMATION_TIME = 750;

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, reactive: true });
        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 _getRealActorScale(actor) {
    let scale = 1.0;
    while (actor) {
        scale *= actor.scale_x;
        actor = actor.get_parent();
    }
    return scale;
}

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,
            timeoutThreshold: 0,
            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._dragTimeoutThreshold = params.timeoutThreshold;

        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;
    }

    _onButtonPress(actor, event) {
        if (event.get_button() != 1)
            return Clutter.EVENT_PROPAGATE;

        this._buttonDown = true;
        this._grabActor(event.get_device());

        let [stageX, stageY] = event.get_coords();
        this._dragStartX = stageX;
        this._dragStartY = stageY;
        this._dragStartTime = event.get_time();
        this._dragThresholdIgnored = false;

        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;

        this._buttonDown = true;
        this._grabActor(event.get_device(), event.get_event_sequence());
        this._dragStartTime = event.get_time();
        this._dragThresholdIgnored = false;

        let [stageX, stageY] = event.get_coords();
        this._dragStartX = stageX;
        this._dragStartY = stageY;

        return Clutter.EVENT_PROPAGATE;
    }

    _grabDevice(actor, pointer, touchSequence) {
        this._grab = global.stage.grab(actor);
        this._grabbedDevice = pointer;
        this._touchSequence = touchSequence;
    }

    _ungrabDevice() {
        if (this._grab) {
            this._grab.dismiss();
            this._grab = null;
        }
        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._eventsGrab) {
            let grab = Main.pushModal(_getEventHandlerActor());
            if ((grab.get_seat_state() & Clutter.GrabState.POINTER) !== 0) {
                this._grabDevice(_getEventHandlerActor(), device, touchSequence);
                this._eventsGrab = grab;
            } else {
                Main.popModal(grab);
            }
        }
    }

    _ungrabEvents() {
        if (this._eventsGrab) {
            this._ungrabDevice();
            Main.popModal(this._eventsGrab);
            this._eventsGrab = null;
        }
    }

    _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.KEY_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:
     * @param {number} stageX: X coordinate of event
     * @param {number} stageY: Y coordinate of event
     * @param {number} time: Event timestamp
     * @param {Clutter.EventSequence=} sequence: Event sequence
     * @param {Clutter.InputDevice=} device: device that originated the event
     *
     * 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 seat = Clutter.get_default_backend().get_default_seat();
                device = seat.get_pointer();
            }
        }

        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;

        let scaledWidth, scaledHeight;

        if (this.actor._delegate && this.actor._delegate.getDragActor) {
            this._dragActor = this.actor._delegate.getDragActor();
            Main.uiGroup.add_child(this._dragActor);
            Main.uiGroup.set_child_above_sibling(this._dragActor, null);
            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);

                this._dragActorSourceDestroyId = this._dragActorSource.connect('destroy', () => {
                    this._dragActorSource = null;
                });
            } else {
                this._dragActorSource = this.actor;
            }
            this._dragOrigParent = undefined;

            this._dragOffsetX = this._dragActor.x - this._dragStartX;
            this._dragOffsetY = this._dragActor.y - this._dragStartY;

            [scaledWidth, scaledHeight] = this._dragActor.get_transformed_size();
        } else {
            this._dragActor = this.actor;

            this._dragActorSource = undefined;
            this._dragOrigParent = this.actor.get_parent();
            this._dragActorHadFixedPos = this._dragActor.fixed_position_set;
            this._dragOrigX = this._dragActor.allocation.x1;
            this._dragOrigY = this._dragActor.allocation.y1;
            this._dragActorHadNatWidth = this._dragActor.natural_width_set;
            this._dragActorHadNatHeight = this._dragActor.natural_height_set;
            this._dragOrigWidth = this._dragActor.allocation.get_width();
            this._dragOrigHeight = this._dragActor.allocation.get_height();
            this._dragOrigScale = this._dragActor.scale_x;

            // Ensure actors with an allocation smaller than their natural size
            // retain their size
            this._dragActor.set_size(...this._dragActor.allocation.get_size());

            const transformedExtents = this._dragActor.get_transformed_extents();

            this._dragOffsetX = transformedExtents.origin.x - this._dragStartX;
            this._dragOffsetY = transformedExtents.origin.y - this._dragStartY;

            scaledWidth = transformedExtents.get_width();
            scaledHeight = transformedExtents.get_height();

            this._dragActor.scale_x = scaledWidth / this._dragOrigWidth;
            this._dragActor.scale_y = scaledHeight / this._dragOrigHeight;

            this._dragOrigParent.remove_actor(this._dragActor);
            Main.uiGroup.add_child(this._dragActor);
            Main.uiGroup.set_child_above_sibling(this._dragActor, null);
            Shell.util_set_hidden_from_pick(this._dragActor, true);

            this._dragOrigParentDestroyId = this._dragOrigParent.connect('destroy', () => {
                this._dragOrigParent = null;
            });
        }

        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;

        let origDragOffsetX = this._dragOffsetX;
        let origDragOffsetY = this._dragOffsetY;
        let [transX, transY] = this._dragActor.get_translation();
        this._dragOffsetX -= transX;
        this._dragOffsetY -= transY;

        this._dragActor.set_position(
            this._dragX + this._dragOffsetX,
            this._dragY + this._dragOffsetY);

        if (this._dragActorMaxSize != undefined) {
            let currentSize = Math.max(scaledWidth, scaledHeight);
            if (currentSize > this._dragActorMaxSize) {
                let scale = this._dragActorMaxSize / currentSize;
                let origScale =  this._dragActor.scale_x;

                // 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
                // a ::new-frame handler.
                this._dragActor.ease({
                    scale_x: scale * origScale,
                    scale_y: scale * origScale,
                    duration: SCALE_ANIMATION_TIME,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    onComplete: () => {
                        this._updateActorPosition(origScale,
                            origDragOffsetX, origDragOffsetY, transX, transY);
                    },
                });

                this._dragActor.get_transition('scale-x').connect('new-frame', () => {
                    this._updateActorPosition(origScale,
                        origDragOffsetX, origDragOffsetY, transX, transY);
                });
            }
        }
    }

    _updateActorPosition(origScale, origDragOffsetX, origDragOffsetY, transX, transY) {
        const currentScale = this._dragActor.scale_x / origScale;
        this._dragOffsetX = currentScale * origDragOffsetX - transX;
        this._dragOffsetY = currentScale * origDragOffsetY - transY;
        this._dragActor.set_position(
            this._dragX + this._dragOffsetX,
            this._dragY + this._dragOffsetY);
    }

    _maybeStartDrag(event) {
        let [stageX, stageY] = event.get_coords();

        if (this._dragThresholdIgnored)
            return Clutter.EVENT_PROPAGATE;

        // 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)) {
            const deviceType = event.get_source_device().get_device_type();
            const isPointerOrTouchpad =
                deviceType === Clutter.InputDeviceType.POINTER_DEVICE ||
                deviceType === Clutter.InputDeviceType.TOUCHPAD_DEVICE;
            const ellapsedTime = event.get_time() - this._dragStartTime;

            // Pointer devices (e.g. mouse) start the drag immediately
            if (isPointerOrTouchpad || ellapsedTime > this._dragTimeoutThreshold) {
                this.startDrag(stageX, stageY, event.get_time(), this._touchSequence, event.get_device());
                this._updateDragPosition(event);
            } else {
                this._dragThresholdIgnored = true;
                this._ungrabActor();
                return Clutter.EVENT_PROPAGATE;
            }
        }

        return Clutter.EVENT_STOP;
    }

    _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);
                let accepted = false;
                try {
                    accepted = target._delegate.acceptDrop(this.actor._delegate,
                        this._dragActor, targX, targY, event.get_time());
                } catch (e) {
                    // On error, skip this target
                    logError(e, "Skipping drag target");
                }
                if (accepted) {
                    // 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] = this._dragActorSource.get_transformed_size();
            scale = sourceScaledWidth ? sourceScaledWidth / this._dragActor.width : 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 parentScale = _getRealActorScale(this._dragOrigParent);

            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,
            duration: 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, {
            duration: REVERT_ANIMATION_TIME,
        });
    }

    _animateDragEnd(eventTime, params) {
        this._animationInProgress = true;

        // start the animation
        this._dragActor.ease(Object.assign(params, {
            opacity: this._dragOrigOpacity,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._onAnimationComplete(this._dragActor, eventTime);
            },
        }));
    }

    _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);
            if (this._dragActorHadFixedPos)
                dragActor.set_position(this._dragOrigX, this._dragOrigY);
            else
                dragActor.fixed_position_set = false;
            if (this._dragActorHadNatWidth)
                this._dragActor.set_width(-1);
            if (this._dragActorHadNatHeight)
                this._dragActor.set_height(-1);
        } 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();

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

        if (this._dragActor) {
            this._dragActor.disconnect(this._dragActorDestroyId);
            this._dragActor = null;
        }

        if (this._dragOrigParent) {
            this._dragOrigParent.disconnect(this._dragOrigParentDestroyId);
            this._dragOrigParent = null;
        }

        if (this._dragActorSource) {
            this._dragActorSource.disconnect(this._dragActorSourceDestroyId);
            this._dragActorSource = null;
        }

        this._dragState = DragState.INIT;
        currentDraggable = null;
    }
};
Signals.addSignalMethods(_Draggable.prototype);

/**
 * makeDraggable:
 * @param {Clutter.Actor} actor: Source actor
 * @param {Object=} params: Additional parameters
 * @returns {Object} a new Draggable
 *
 * 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)telepathyClient.jsƈ// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */

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

var Tpl = null;
var Tp = null;
try {
    ({ TelepathyGLib: Tp, TelepathyLogger: Tpl } = imports.gi);

    Gio._promisify(Tp.Channel.prototype, 'close_async');
    Gio._promisify(Tp.TextChannel.prototype, 'send_message_async');
    Gio._promisify(Tp.ChannelDispatchOperation.prototype, 'claim_with_async');
    Gio._promisify(Tpl.LogManager.prototype, 'get_filtered_events_async');
} 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',
};

const ChatMessage = HAVE_TP ? GObject.registerClass({
    Properties: {
        'message-type': GObject.ParamSpec.int(
            'message-type', 'message-type', 'message-type',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            Math.min(...Object.values(Tp.ChannelTextMessageType)),
            Math.max(...Object.values(Tp.ChannelTextMessageType)),
            Tp.ChannelTextMessageType.NORMAL),
        'text': GObject.ParamSpec.string(
            'text', 'text', 'text',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            null),
        'sender': GObject.ParamSpec.string(
            'sender', 'sender', 'sender',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            null),
        'timestamp': GObject.ParamSpec.int64(
            'timestamp', 'timestamp', 'timestamp',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            0, Number.MAX_SAFE_INTEGER, 0),
        'direction': GObject.ParamSpec.string(
            'direction', 'direction', 'direction',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            null),
    },
}, class ChatMessageClass extends GObject.Object {
    static newFromTpMessage(tpMessage, direction) {
        return new ChatMessage({
            'message-type': tpMessage.get_message_type(),
            'text': tpMessage.to_text()[0],
            'sender': tpMessage.sender.alias,
            'timestamp': direction === NotificationDirection.RECEIVED
                ? tpMessage.get_received_timestamp() : tpMessage.get_sent_timestamp(),
            direction,
        });
    }

    static newFromTplTextEvent(tplTextEvent) {
        let direction =
            tplTextEvent.get_sender().get_entity_type() === Tpl.EntityType.SELF
                ? NotificationDirection.SENT : NotificationDirection.RECEIVED;

        return new ChatMessage({
            'message-type': tplTextEvent.get_message_type(),
            'text': tplTextEvent.get_message(),
            'sender': tplTextEvent.get_sender().get_alias(),
            'timestamp': tplTextEvent.get_timestamp(),
            direction,
        });
    }
}) : null;


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(`Could not register Telepathy client. Error: ${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 preempt our channels if
        // needed
        this.set_delegated_channels_callback(
            this._delegatedChannelsCb.bind(this));
    }

    vfunc_observe_channels(...args) {
        let [account, conn, channels, dispatchOp_, requests_, context] = args;
        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(...args) {
        let [account, conn, channels, requests_, userActionTime_, context] = args;
        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();
                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.showNotification();
            }
        }
    }

    vfunc_add_dispatch_operation(...args) {
        let [account, conn, channels, dispatchOp, context] = args;
        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',
            }));
        }
    }

    async _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;
        }

        context.accept();

        // Approve private text channels right away as we are going to handle it
        try {
            await dispatchOp.claim_with_async(this);
            this._handlingChannels(account, conn, [channel], false);
        } catch (err) {
            log(`Failed to claim channel: ${err}`);
        }
    }

    _delegatedChannelsCb(_client, _channels) {
        // Nothing to do as we don't make a distinction between observed and
        // handled channels.
    }
}) : null;

var ChatSource = HAVE_TP ? GObject.registerClass(
class ChatSource extends MessageTray.Source {
    _init(account, conn, channel, contact, client) {
        this._account = account;
        this._contact = contact;
        this._client = client;

        super._init(contact.get_alias());

        this.isChat = true;
        this._pendingMessages = [];

        this._conn = conn;
        this._channel = channel;

        this._notifyTimeoutId = 0;

        this._presence = contact.get_presence_type();

        this._channel.connectObject(
            'invalidated', this._channelClosed.bind(this),
            'message-sent', this._messageSent.bind(this),
            'message-received', this._messageReceived.bind(this),
            'pending-message-removed', this._pendingRemoved.bind(this), this);

        this._contact.connectObject(
            'notify::alias', this._updateAlias.bind(this),
            'notify::avatar-file', this._updateAvatarIcon.bind(this),
            'presence-changed', this._presenceChanged.bind(this), this);

        // Add ourselves as a source.
        Main.messageTray.add(this);

        this._getLogMessages();
    }

    _ensureNotification() {
        if (this._notification)
            return;

        this._notification = new ChatNotification(this);
        this._notification.connectObject(
            'activated', this.open.bind(this),
            'destroy', () => (this._notification = null),
            'updated', () => {
                if (this._banner && this._banner.expanded)
                    this._ackMessages();
            }, this);
        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
        this._banner.connectObject(
            'expanded', this._ackMessages.bind(this),
            'destroy', () => (this._banner = null), this);

        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 });
        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._notification) {
            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 available - 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);
        }
    }

    async _getLogMessages() {
        let logManager = Tpl.LogManager.dup_singleton();
        let entity = Tpl.Entity.new_from_tp_contact(this._contact, Tpl.EntityType.CONTACT);

        const [events] = await logManager.get_filtered_events_async(
            this._account, entity,
            Tpl.EventTypeMask.TEXT, SCROLLBACK_HISTORY_LINES,
            null);

        let logMessages = events.map(e => ChatMessage.newFromTplTextEvent(e));
        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(ChatMessage.newFromTpMessage(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.showNotification();
    }

    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();
        } 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.disconnectObject(this);
        this._contact.disconnectObject(this);

        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 = ChatMessage.newFromTpMessage(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)
            GLib.source_remove(this._notifyTimeoutId);
        this._notifyTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500,
            this._notifyTimeout.bind(this));
        GLib.Source.set_name_by_id(this._notifyTimeoutId, '[gnome-shell] this._notifyTimeout');
    }

    _notifyTimeout() {
        if (this._pendingMessages.length != 0)
            this.showNotification();

        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 = ChatMessage.newFromTpMessage(message,
            NotificationDirection.SENT);
        this._notification.appendMessage(message);
    }

    showNotification() {
        super.showNotification(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);
    }

    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);
    }
}) : null;

const ChatNotificationMessage = HAVE_TP ? GObject.registerClass(
class ChatNotificationMessage extends GObject.Object {
    _init(props = {}) {
        super._init();
        this.set(props);
    }
}) : null;

var ChatNotification = HAVE_TP ? GObject.registerClass({
    Signals: {
        'message-removed': { param_types: [ChatNotificationMessage.$gtype] },
        'message-added': { param_types: [ChatNotificationMessage.$gtype] },
        'timestamp-changed': { param_types: [ChatNotificationMessage.$gtype] },
    },
}, class ChatNotification extends MessageTray.Notification {
    _init(source) {
        super._init(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)
            GLib.source_remove(this._timestampTimeoutId);
        this._timestampTimeoutId = 0;
        super.destroy(reason);
    }

    /**
     * appendMessage:
     * @param {Object} message: An object with the properties
     *   {string} message.text: the body of the message,
     *   {Tp.ChannelTextMessageType} message.messageType: the type
     *   {string} message.sender: the name of the sender,
     *   {number} message.timestamp: the time the message was sent
     *   {NotificationDirection} message.direction: a #NotificationDirection
     *
     * @param {bool} 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>${senderAlias}</i> ${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,
            styles,
            timestamp: message.timestamp,
            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:
     * @param {Object} props: An object with the properties:
     *  {string} props.body: The text of the message.
     *  {string} props.group: The group of the message, one of:
     *         'received', 'sent', 'meta'.
     *  {string[]} props.styles: Style class names for the message to have.
     *  {number} props.timestamp: The timestamp of the message.
     *  {bool} props.noTimestamp: suppress timestamp signal?
     */
    _append(props) {
        let currentTime = Date.now() / 1000;
        props = Params.parse(props, {
            body: null,
            group: null,
            styles: [],
            timestamp: currentTime,
            noTimestamp: false,
        });
        const { noTimestamp } = props;
        delete props.noTimestamp;

        // Reset the old message timeout
        if (this._timestampTimeoutId)
            GLib.source_remove(this._timestampTimeoutId);
        this._timestampTimeoutId = 0;

        let message = new ChatNotificationMessage({
            realMessage: props.group !== 'meta',
            showTimestamp: false,
            ...props,
        });

        this.messages.unshift(message);
        this.emit('message-added', message);

        if (!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 = GLib.timeout_add_seconds(
                    GLib.PRIORITY_DEFAULT,
                    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. */
        const message = `<i>${
            _('%s is now known as %s').format(oldAlias, newAlias)}</i>`;

        this._append({
            body: message,
            group: 'meta',
            styles: ['chat-meta-message'],
        });

        this._filterMessages();
    }
}) : null;

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 = GObject.registerClass(
class ChatNotificationBanner extends MessageTray.NotificationBanner {
    _init(notification) {
        super._init(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.notification.connectObject(
            'timestamp-changed', (n, message) => this._updateTimestamp(message),
            'message-added', (n, message) => this._addMessage(message),
            'message-removed', (n, message) => {
                let actor = this._messageActors.get(message);
                if (this._messageActors.delete(message))
                    actor.destroy();
            }, this);

        for (let i = this.notification.messages.length - 1; i >= 0; i--)
            this._addMessage(this.notification.messages[i]);
    }

    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 body = new MessageList.URLHighlighter(message.body, true, true);

        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) {
            GLib.source_remove(this._composingTimeoutId);
            this._composingTimeoutId = 0;
        }

        if (text != '') {
            this.notification.source.setChatState(Tp.ChannelChatState.COMPOSING);

            this._composingTimeoutId = GLib.timeout_add_seconds(
                GLib.PRIORITY_DEFAULT,
                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)iconGrid.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported BaseIcon, IconGrid, IconGridLayout */

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

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

var ICON_SIZE = 96;

var PAGE_SWITCH_TIME = 300;

var IconSize = {
    LARGE: 96,
    MEDIUM: 64,
    MEDIUM_SMALL: 48,
    SMALL: 32,
    SMALLER: 24,
    TINY: 16,
};

var APPICON_ANIMATION_OUT_SCALE = 3;
var APPICON_ANIMATION_OUT_TIME = 250;

const ICON_POSITION_DELAY = 10;

const defaultGridModes = [
    {
        rows: 8,
        columns: 3,
    },
    {
        rows: 6,
        columns: 4,
    },
    {
        rows: 4,
        columns: 6,
    },
    {
        rows: 3,
        columns: 8,
    },
];

var LEFT_DIVIDER_LEEWAY = 20;
var RIGHT_DIVIDER_LEEWAY = 20;

var DragLocation = {
    INVALID: 0,
    START_EDGE: 1,
    ON_ICON: 2,
    END_EDGE: 3,
    EMPTY_SPACE: 4,
};

var BaseIcon = GObject.registerClass(
class BaseIcon extends Shell.SquareBin {
    _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 });

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

        this.iconSize = ICON_SIZE;
        this._iconBin = new St.Bin({ x_align: Clutter.ActorAlign.CENTER });

        this._box.add_actor(this._iconBin);

        if (params.showLabel) {
            this.label = new St.Label({ text: label });
            this.label.clutter_text.set({
                x_align: Clutter.ActorAlign.CENTER,
                y_align: Clutter.ActorAlign.CENTER,
            });
            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();
        cache.connectObject(
            'icon-theme-changed', this._onIconThemeChanged.bind(this), this);
    }

    // This can be overridden by a subclass, or by the createIcon
    // parameter to _init()
    createIcon(_size) {
        throw new GObject.NotImplementedError(`createIcon in ${this.constructor.name}`);
    }

    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 {
            const { scaleFactor } =
                St.ThemeContext.get_for_stage(global.stage);

            let [found, len] = node.lookup_length('icon-size', false);
            size = found ? len / scaleFactor : ICON_SIZE;
        }

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

        this._createIconTexture(size);
    }

    _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);
    }

    animateZoomOutAtPos(x, y) {
        zoomOutActorAtPos(this.child, x, y);
    }

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

function zoomOutActor(actor) {
    let [x, y] = actor.get_transformed_position();
    zoomOutActorAtPos(actor, x, y);
}

function zoomOutActorAtPos(actor, x, y) {
    const monitor = Main.layoutManager.findMonitorForActor(actor);
    if (!monitor)
        return;

    const actorClone = new Clutter.Clone({
        source: actor,
        reactive: false,
    });
    let [width, height] = actor.get_transformed_size();

    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 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 = Math.clamp(scaledX, monitor.x, monitor.x + monitor.width - scaledWidth);
    let containedY = Math.clamp(scaledY, monitor.y, monitor.y + monitor.height - scaledHeight);

    actorClone.ease({
        scale_x: APPICON_ANIMATION_OUT_SCALE,
        scale_y: APPICON_ANIMATION_OUT_SCALE,
        translation_x: containedX - scaledX,
        translation_y: containedY - scaledY,
        opacity: 0,
        duration: APPICON_ANIMATION_OUT_TIME,
        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        onComplete: () => actorClone.destroy(),
    });
}

function animateIconPosition(icon, box, nChangedIcons) {
    if (!icon.has_allocation() || icon.allocation.equal(box) || icon.opacity === 0) {
        icon.allocate(box);
        return false;
    }

    icon.save_easing_state();
    icon.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
    icon.set_easing_delay(nChangedIcons * ICON_POSITION_DELAY);

    icon.allocate(box);

    icon.restore_easing_state();

    return true;
}

function swap(value, length) {
    return length - value - 1;
}

var IconGridLayout = GObject.registerClass({
    Properties: {
        'allow-incomplete-pages': GObject.ParamSpec.boolean('allow-incomplete-pages',
            'Allow incomplete pages', 'Allow incomplete pages',
            GObject.ParamFlags.READWRITE,
            true),
        'column-spacing': GObject.ParamSpec.int('column-spacing',
            'Column spacing', 'Column spacing',
            GObject.ParamFlags.READWRITE,
            0, GLib.MAXINT32, 0),
        'columns-per-page': GObject.ParamSpec.int('columns-per-page',
            'Columns per page', 'Columns per page',
            GObject.ParamFlags.READWRITE,
            1, GLib.MAXINT32, 6),
        'fixed-icon-size': GObject.ParamSpec.int('fixed-icon-size',
            'Fixed icon size', 'Fixed icon size',
            GObject.ParamFlags.READWRITE,
            -1, GLib.MAXINT32, -1),
        'icon-size': GObject.ParamSpec.int('icon-size',
            'Icon size', 'Icon size',
            GObject.ParamFlags.READABLE,
            0, GLib.MAXINT32, 0),
        'last-row-align': GObject.ParamSpec.enum('last-row-align',
            'Last row align', 'Last row align',
            GObject.ParamFlags.READWRITE,
            Clutter.ActorAlign.$gtype,
            Clutter.ActorAlign.FILL),
        'max-column-spacing': GObject.ParamSpec.int('max-column-spacing',
            'Maximum column spacing', 'Maximum column spacing',
            GObject.ParamFlags.READWRITE,
            -1, GLib.MAXINT32, -1),
        'max-row-spacing': GObject.ParamSpec.int('max-row-spacing',
            'Maximum row spacing', 'Maximum row spacing',
            GObject.ParamFlags.READWRITE,
            -1, GLib.MAXINT32, -1),
        'orientation': GObject.ParamSpec.enum('orientation',
            'Orientation', 'Orientation',
            GObject.ParamFlags.READWRITE,
            Clutter.Orientation.$gtype,
            Clutter.Orientation.VERTICAL),
        'page-halign': GObject.ParamSpec.enum('page-halign',
            'Horizontal page align',
            'Horizontal page align',
            GObject.ParamFlags.READWRITE,
            Clutter.ActorAlign.$gtype,
            Clutter.ActorAlign.FILL),
        'page-padding': GObject.ParamSpec.boxed('page-padding',
            'Page padding', 'Page padding',
            GObject.ParamFlags.READWRITE,
            Clutter.Margin.$gtype),
        'page-valign': GObject.ParamSpec.enum('page-valign',
            'Vertical page align',
            'Vertical page align',
            GObject.ParamFlags.READWRITE,
            Clutter.ActorAlign.$gtype,
            Clutter.ActorAlign.FILL),
        'row-spacing': GObject.ParamSpec.int('row-spacing',
            'Row spacing', 'Row spacing',
            GObject.ParamFlags.READWRITE,
            0, GLib.MAXINT32, 0),
        'rows-per-page': GObject.ParamSpec.int('rows-per-page',
            'Rows per page', 'Rows per page',
            GObject.ParamFlags.READWRITE,
            1, GLib.MAXINT32, 4),
    },
    Signals: {
        'pages-changed': {},
    },
}, class IconGridLayout extends Clutter.LayoutManager {
    _init(params = {}) {
        this._orientation = params.orientation ?? Clutter.Orientation.VERTICAL;

        super._init(params);

        if (!this.pagePadding)
            this.pagePadding = new Clutter.Margin();

        this._iconSize = this.fixedIconSize !== -1
            ? this.fixedIconSize
            : IconSize.LARGE;

        this._pageSizeChanged = false;
        this._pageHeight = 0;
        this._pageWidth = 0;
        this._nPages = -1;

        // [
        //     {
        //         children: [ itemData, itemData, itemData, ... ],
        //     },
        //     {
        //         children: [ itemData, itemData, itemData, ... ],
        //     },
        //     {
        //         children: [ itemData, itemData, itemData, ... ],
        //     },
        // ]
        this._pages = [];

        // {
        //     item: {
        //         actor: Clutter.Actor,
        //         pageIndex: <index>,
        //     },
        //     item: {
        //         actor: Clutter.Actor,
        //         pageIndex: <index>,
        //     },
        // }
        this._items = new Map();

        this._containerDestroyedId = 0;
        this._updateIconSizesLaterId = 0;

        this._childrenMaxSize = -1;
    }

    _findBestIconSize() {
        const nColumns = this.columnsPerPage;
        const nRows = this.rowsPerPage;
        const columnSpacingPerPage = this.columnSpacing * (nColumns - 1);
        const rowSpacingPerPage = this.rowSpacing * (nRows - 1);
        const [firstItem] = this._container;

        if (this.fixedIconSize !== -1)
            return this.fixedIconSize;

        const iconSizes = Object.values(IconSize).sort((a, b) => b - a);
        for (const size of iconSizes) {
            let usedWidth, usedHeight;

            if (firstItem) {
                firstItem.icon.setIconSize(size);
                const [firstItemWidth, firstItemHeight] =
                    firstItem.get_preferred_size();

                const itemSize = Math.max(firstItemWidth, firstItemHeight);

                usedWidth = itemSize * nColumns;
                usedHeight = itemSize * nRows;
            } else {
                usedWidth = size * nColumns;
                usedHeight = size * nRows;
            }

            const emptyHSpace =
                this._pageWidth - usedWidth - columnSpacingPerPage -
                this.pagePadding.left - this.pagePadding.right;
            const emptyVSpace =
                this._pageHeight - usedHeight -  rowSpacingPerPage -
                this.pagePadding.top - this.pagePadding.bottom;

            if (emptyHSpace >= 0 && emptyVSpace > 0)
                return size;
        }

        return IconSize.TINY;
    }

    _getChildrenMaxSize() {
        if (this._childrenMaxSize === -1) {
            let minWidth = 0;
            let minHeight = 0;

            const nPages = this._pages.length;
            for (let pageIndex = 0; pageIndex < nPages; pageIndex++) {
                const page = this._pages[pageIndex];
                const nVisibleItems = page.visibleChildren.length;
                for (let itemIndex = 0; itemIndex < nVisibleItems; itemIndex++) {
                    const item = page.visibleChildren[itemIndex];

                    const childMinHeight = item.get_preferred_height(-1)[0];
                    const childMinWidth = item.get_preferred_width(-1)[0];

                    minWidth = Math.max(minWidth, childMinWidth);
                    minHeight = Math.max(minHeight, childMinHeight);
                }
            }

            this._childrenMaxSize = Math.max(minWidth, minHeight);
        }

        return this._childrenMaxSize;
    }

    _updateVisibleChildrenForPage(pageIndex) {
        this._pages[pageIndex].visibleChildren =
            this._pages[pageIndex].children.filter(actor => actor.visible);
    }

    _updatePages() {
        for (let i = 0; i < this._pages.length; i++)
            this._relocateSurplusItems(i);
    }

    _unlinkItem(item) {
        const itemData = this._items.get(item);

        item.disconnect(itemData.destroyId);
        item.disconnect(itemData.visibleId);
        item.disconnect(itemData.queueRelayoutId);

        this._items.delete(item);
    }

    _removePage(pageIndex) {
        // Make sure to not leave any icon left here
        this._pages[pageIndex].children.forEach(item => {
            this._unlinkItem(item);
        });

        // Adjust the page indexes of items after this page
        for (const itemData of this._items.values()) {
            if (itemData.pageIndex > pageIndex)
                itemData.pageIndex--;
        }

        this._pages.splice(pageIndex, 1);
        this.emit('pages-changed');
    }

    _fillItemVacancies(pageIndex) {
        if (pageIndex >= this._pages.length - 1)
            return;

        const visiblePageItems = this._pages[pageIndex].visibleChildren;
        const itemsPerPage = this.columnsPerPage * this.rowsPerPage;

        // No reduce needed
        if (visiblePageItems.length === itemsPerPage)
            return;

        const visibleNextPageItems = this._pages[pageIndex + 1].visibleChildren;
        const nMissingItems = Math.min(itemsPerPage - visiblePageItems.length, visibleNextPageItems.length);

        // Append to the current page the first items of the next page
        for (let i = 0; i < nMissingItems; i++) {
            const reducedItem = visibleNextPageItems[i];

            this._removeItemData(reducedItem);
            this._addItemToPage(reducedItem, pageIndex, -1);
        }
    }

    _removeItemData(item) {
        const itemData = this._items.get(item);
        const pageIndex = itemData.pageIndex;
        const page = this._pages[pageIndex];
        const itemIndex = page.children.indexOf(item);

        this._unlinkItem(item);

        page.children.splice(itemIndex, 1);

        this._updateVisibleChildrenForPage(pageIndex);

        // Delete the page if this is the last icon in it
        const visibleItems = this._pages[pageIndex].visibleChildren;
        if (visibleItems.length === 0)
            this._removePage(pageIndex);

        if (!this.allowIncompletePages)
            this._fillItemVacancies(pageIndex);
    }

    _relocateSurplusItems(pageIndex) {
        const visiblePageItems = this._pages[pageIndex].visibleChildren;
        const itemsPerPage = this.columnsPerPage * this.rowsPerPage;

        // No overflow needed
        if (visiblePageItems.length <= itemsPerPage)
            return;

        const nExtraItems = visiblePageItems.length - itemsPerPage;
        for (let i = 0; i < nExtraItems; i++) {
            const overflowIndex = visiblePageItems.length - i - 1;
            const overflowItem = visiblePageItems[overflowIndex];

            this._removeItemData(overflowItem);
            this._addItemToPage(overflowItem, pageIndex + 1, 0);
        }
    }

    _appendPage() {
        this._pages.push({ children: [] });
        this.emit('pages-changed');
    }

    _addItemToPage(item, pageIndex, index) {
        // Ensure we have at least one page
        if (this._pages.length === 0)
            this._appendPage();

        // Append a new page if necessary
        if (pageIndex === this._pages.length)
            this._appendPage();

        if (pageIndex === -1)
            pageIndex = this._pages.length - 1;

        if (index === -1)
            index = this._pages[pageIndex].children.length;

        this._items.set(item, {
            actor: item,
            pageIndex,
            destroyId: item.connect('destroy', () => this._removeItemData(item)),
            visibleId: item.connect('notify::visible', () => {
                const itemData = this._items.get(item);

                this._updateVisibleChildrenForPage(itemData.pageIndex);

                if (item.visible)
                    this._relocateSurplusItems(itemData.pageIndex);
                else if (!this.allowIncompletePages)
                    this._fillItemVacancies(itemData.pageIndex);
            }),
            queueRelayoutId: item.connect('queue-relayout', () => {
                this._childrenMaxSize = -1;
            }),
        });

        item.icon.setIconSize(this._iconSize);

        this._pages[pageIndex].children.splice(index, 0, item);
        this._updateVisibleChildrenForPage(pageIndex);
        this._relocateSurplusItems(pageIndex);
    }

    _calculateSpacing(childSize) {
        const nColumns = this.columnsPerPage;
        const nRows = this.rowsPerPage;
        const usedWidth = childSize * nColumns;
        const usedHeight = childSize * nRows;
        const columnSpacingPerPage = this.columnSpacing * (nColumns - 1);
        const rowSpacingPerPage = this.rowSpacing * (nRows - 1);

        const emptyHSpace =
            this._pageWidth - usedWidth - columnSpacingPerPage -
            this.pagePadding.left - this.pagePadding.right;
        const emptyVSpace =
            this._pageHeight - usedHeight -  rowSpacingPerPage -
            this.pagePadding.top - this.pagePadding.bottom;
        let leftEmptySpace = this.pagePadding.left;
        let topEmptySpace = this.pagePadding.top;
        let hSpacing;
        let vSpacing;

        switch (this.pageHalign) {
        case Clutter.ActorAlign.START:
            hSpacing = this.columnSpacing;
            break;
        case Clutter.ActorAlign.CENTER:
            leftEmptySpace += Math.floor(emptyHSpace / 2);
            hSpacing = this.columnSpacing;
            break;
        case Clutter.ActorAlign.END:
            leftEmptySpace += emptyHSpace;
            hSpacing = this.columnSpacing;
            break;
        case Clutter.ActorAlign.FILL:
            hSpacing = this.columnSpacing + emptyHSpace / (nColumns - 1);

            // Maybe constraint horizontal spacing
            if (this.maxColumnSpacing !== -1 && hSpacing > this.maxColumnSpacing) {
                const extraHSpacing =
                    (this.maxColumnSpacing - this.columnSpacing) * (nColumns - 1);

                hSpacing = this.maxColumnSpacing;
                leftEmptySpace +=
                    Math.max((emptyHSpace - extraHSpacing) / 2, 0);
            }
            break;
        }

        switch (this.pageValign) {
        case Clutter.ActorAlign.START:
            vSpacing = this.rowSpacing;
            break;
        case Clutter.ActorAlign.CENTER:
            topEmptySpace += Math.floor(emptyVSpace / 2);
            vSpacing = this.rowSpacing;
            break;
        case Clutter.ActorAlign.END:
            topEmptySpace += emptyVSpace;
            vSpacing = this.rowSpacing;
            break;
        case Clutter.ActorAlign.FILL:
            vSpacing = this.rowSpacing + emptyVSpace / (nRows - 1);

            // Maybe constraint vertical spacing
            if (this.maxRowSpacing !== -1 && vSpacing > this.maxRowSpacing) {
                const extraVSpacing =
                    (this.maxRowSpacing - this.rowSpacing) * (nRows - 1);

                vSpacing = this.maxRowSpacing;
                topEmptySpace +=
                    Math.max((emptyVSpace - extraVSpacing) / 2, 0);
            }

            break;
        }

        return [leftEmptySpace, topEmptySpace, hSpacing, vSpacing];
    }

    _getRowPadding(align, items, itemIndex, childSize, spacing) {
        if (align === Clutter.ActorAlign.START ||
            align === Clutter.ActorAlign.FILL)
            return 0;

        const nRows = Math.ceil(items.length / this.columnsPerPage);

        let rowAlign = 0;
        const row = Math.floor(itemIndex / this.columnsPerPage);

        // Only apply to the last row
        if (row < nRows - 1)
            return 0;

        const rowStart = row * this.columnsPerPage;
        const rowEnd = Math.min((row + 1) * this.columnsPerPage - 1, items.length - 1);
        const itemsInThisRow = rowEnd - rowStart + 1;
        const nEmpty = this.columnsPerPage - itemsInThisRow;
        const availableWidth = nEmpty * (spacing + childSize);

        const isRtl =
            Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;

        switch (align) {
        case Clutter.ActorAlign.CENTER:
            rowAlign = availableWidth / 2;
            break;
        case Clutter.ActorAlign.END:
            rowAlign = availableWidth;
            break;
        // START and FILL align are handled at the beginning of the function
        }

        return isRtl ? rowAlign * -1 : rowAlign;
    }

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

    vfunc_set_container(container) {
        this._container?.disconnectObject(this);

        this._container = container;

        if (this._container)
            this._container.connectObject('destroy', this._onDestroy.bind(this), this);
    }

    vfunc_get_preferred_width(_container, _forHeight) {
        let minWidth = -1;
        let natWidth = -1;

        switch (this._orientation) {
        case Clutter.Orientation.VERTICAL:
            minWidth = IconSize.TINY;
            natWidth = this._pageWidth;
            break;

        case Clutter.Orientation.HORIZONTAL:
            minWidth = this._pageWidth * this._pages.length;
            natWidth = minWidth;
            break;
        }

        return [minWidth, natWidth];
    }

    vfunc_get_preferred_height(_container, _forWidth) {
        let minHeight = -1;
        let natHeight = -1;

        switch (this._orientation) {
        case Clutter.Orientation.VERTICAL:
            minHeight = this._pageHeight * this._pages.length;
            natHeight = minHeight;
            break;

        case Clutter.Orientation.HORIZONTAL:
            minHeight = IconSize.TINY;
            natHeight = this._pageHeight;
            break;
        }

        return [minHeight, natHeight];
    }

    vfunc_allocate() {
        if (this._pageWidth === 0 || this._pageHeight === 0)
            throw new Error('IconGridLayout.adaptToSize wasn\'t called before allocation');

        const isRtl =
            Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
        const childSize = this._getChildrenMaxSize();

        const [leftEmptySpace, topEmptySpace, hSpacing, vSpacing] =
            this._calculateSpacing(childSize);

        const childBox = new Clutter.ActorBox();

        let nChangedIcons = 0;
        const columnsPerPage = this.columnsPerPage;
        const orientation = this._orientation;
        const pageWidth = this._pageWidth;
        const pageHeight = this._pageHeight;
        const pageSizeChanged = this._pageSizeChanged;
        const lastRowAlign = this.lastRowAlign;
        const shouldEaseItems = this._shouldEaseItems;

        this._pages.forEach((page, pageIndex) => {
            if (isRtl && orientation === Clutter.Orientation.HORIZONTAL)
                pageIndex = swap(pageIndex, this._pages.length);

            page.visibleChildren.forEach((item, itemIndex) => {
                const row = Math.floor(itemIndex / columnsPerPage);
                let column = itemIndex % columnsPerPage;

                if (isRtl)
                    column = swap(column, columnsPerPage);

                const rowPadding = this._getRowPadding(lastRowAlign,
                    page.visibleChildren, itemIndex, childSize, hSpacing);

                // Icon position
                let x = leftEmptySpace + rowPadding + column * (childSize + hSpacing);
                let y = topEmptySpace + row * (childSize + vSpacing);

                // Page start
                switch (orientation) {
                case Clutter.Orientation.HORIZONTAL:
                    x += pageIndex * pageWidth;
                    break;
                case Clutter.Orientation.VERTICAL:
                    y += pageIndex * pageHeight;
                    break;
                }

                childBox.set_origin(Math.floor(x), Math.floor(y));

                const [,, naturalWidth, naturalHeight] = item.get_preferred_size();
                childBox.set_size(
                    Math.max(childSize, naturalWidth),
                    Math.max(childSize, naturalHeight));

                if (!shouldEaseItems || pageSizeChanged)
                    item.allocate(childBox);
                else if (animateIconPosition(item, childBox, nChangedIcons))
                    nChangedIcons++;
            });
        });

        this._pageSizeChanged = false;
        this._shouldEaseItems = false;
    }

    /**
     * addItem:
     * @param {Clutter.Actor} item: item to append to the grid
     * @param {int} page: page number
     * @param {int} index: position in the page
     *
     * Adds @item to the grid. @item must not be part of the grid.
     *
     * If @index exceeds the number of items per page, @item will
     * be added to the next page.
     *
     * @page must be a number between 0 and the number of pages.
     * Adding to the page after next will create a new page.
     */
    addItem(item, page = -1, index = -1) {
        if (this._items.has(item))
            throw new Error(`Item ${item} already added to IconGridLayout`);

        if (page > this._pages.length)
            throw new Error(`Cannot add ${item} to page ${page}`);

        if (!this._container)
            return;

        this._shouldEaseItems = true;

        this._container.add_child(item);
        this._addItemToPage(item, page, index);
    }

    /**
     * appendItem:
     * @param {Clutter.Actor} item: item to append to the grid
     *
     * Appends @item to the grid. @item must not be part of the grid.
     */
    appendItem(item) {
        this.addItem(item);
    }

    /**
     * moveItem:
     * @param {Clutter.Actor} item: item to move
     * @param {int} newPage: new page of the item
     * @param {int} newPosition: new page of the item
     *
     * Moves @item to the grid. @item must be part of the grid.
     */
    moveItem(item, newPage, newPosition) {
        if (!this._items.has(item))
            throw new Error(`Item ${item} is not part of the IconGridLayout`);

        this._shouldEaseItems = true;

        this._removeItemData(item);
        this._addItemToPage(item, newPage, newPosition);
    }

    /**
     * removeItem:
     * @param {Clutter.Actor} item: item to remove from the grid
     *
     * Removes @item to the grid. @item must be part of the grid.
     */
    removeItem(item) {
        if (!this._items.has(item))
            throw new Error(`Item ${item} is not part of the IconGridLayout`);

        if (!this._container)
            return;

        this._shouldEaseItems = true;

        this._container.remove_child(item);
        this._removeItemData(item);
    }

    /**
     * getItemsAtPage:
     * @param {int} pageIndex: page index
     *
     * Retrieves the children at page @pageIndex. Children may be invisible.
     *
     * @returns {Array} an array of {Clutter.Actor}s
     */
    getItemsAtPage(pageIndex) {
        if (pageIndex >= this._pages.length)
            throw new Error(`IconGridLayout does not have page ${pageIndex}`);

        return [...this._pages[pageIndex].children];
    }

    /**
     * getItemPosition:
     * @param {BaseIcon} item: the item
     *
     * Retrieves the position of @item is its page, or -1 if @item is not
     * part of the grid.
     *
     * @returns {[int, int]} the page and position of @item
     */
    getItemPosition(item) {
        if (!this._items.has(item))
            return [-1, -1];

        const itemData = this._items.get(item);
        const visibleItems = this._pages[itemData.pageIndex].visibleChildren;

        return [itemData.pageIndex, visibleItems.indexOf(item)];
    }

    /**
     * getItemAt:
     * @param {int} page: the page
     * @param {int} position: the position in page
     *
     * Retrieves the item at @page and @position.
     *
     * @returns {BaseItem} the item at @page and @position, or null
     */
    getItemAt(page, position) {
        if (page < 0 || page >= this._pages.length)
            return null;

        const visibleItems = this._pages[page].visibleChildren;

        if (position < 0 || position >= visibleItems.length)
            return null;

        return visibleItems[position];
    }

    /**
     * getItemPage:
     * @param {BaseIcon} item: the item
     *
     * Retrieves the page @item is in, or -1 if @item is not part of the grid.
     *
     * @returns {int} the page where @item is in
     */
    getItemPage(item) {
        if (!this._items.has(item))
            return -1;

        const itemData = this._items.get(item);
        return itemData.pageIndex;
    }

    ensureIconSizeUpdated() {
        if (this._updateIconSizesLaterId === 0)
            return Promise.resolve();

        return new Promise(
            resolve => this._iconSizeUpdateResolveCbs.push(resolve));
    }

    adaptToSize(pageWidth, pageHeight) {
        if (this._pageWidth === pageWidth && this._pageHeight === pageHeight)
            return;

        this._pageWidth = pageWidth;
        this._pageHeight = pageHeight;
        this._pageSizeChanged = true;

        if (this._updateIconSizesLaterId === 0) {
            this._updateIconSizesLaterId =
                Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                    const iconSize = this._findBestIconSize();

                    if (this._iconSize !== iconSize) {
                        this._iconSize = iconSize;

                        for (const child of this._container)
                            child.icon.setIconSize(iconSize);

                        this.notify('icon-size');
                    }

                    this._updateIconSizesLaterId = 0;
                    return GLib.SOURCE_REMOVE;
                });
        }
    }

    /**
     * getDropTarget:
     * @param {int} x: position of the horizontal axis
     * @param {int} y: position of the vertical axis
     *
     * Retrieves the item located at (@x, @y), as well as the drag location.
     * Both @x and @y are relative to the grid.
     *
     * @returns {[Clutter.Actor, DragLocation]} the item and drag location
     * under (@x, @y)
     */
    getDropTarget(x, y) {
        const childSize = this._getChildrenMaxSize();
        const [leftEmptySpace, topEmptySpace, hSpacing, vSpacing] =
            this._calculateSpacing(childSize);

        const isRtl =
            Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;

        let page = this._orientation === Clutter.Orientation.VERTICAL
            ? Math.floor(y / this._pageHeight)
            : Math.floor(x / this._pageWidth);

        // Out of bounds
        if (page >= this._pages.length)
            return [null, DragLocation.INVALID];

        if (isRtl && this._orientation === Clutter.Orientation.HORIZONTAL)
            page = swap(page, this._pages.length);

        // Page-relative coordinates from now on
        if (this._orientation === Clutter.Orientation.HORIZONTAL)
            x %= this._pageWidth;
        else
            y %= this._pageHeight;

        if (x < leftEmptySpace || y < topEmptySpace)
            return [null, DragLocation.INVALID];

        const gridWidth =
            childSize * this.columnsPerPage +
            hSpacing * (this.columnsPerPage - 1);
        const gridHeight =
            childSize * this.rowsPerPage +
            vSpacing * (this.rowsPerPage - 1);

        if (x > leftEmptySpace + gridWidth || y > topEmptySpace + gridHeight)
            return [null, DragLocation.INVALID];

        const halfHSpacing = hSpacing / 2;
        const halfVSpacing = vSpacing / 2;
        const visibleItems = this._pages[page].visibleChildren;

        for (const item of visibleItems) {
            const childBox = item.allocation.copy();

            // Page offset
            switch (this._orientation) {
            case Clutter.Orientation.HORIZONTAL:
                childBox.set_origin(childBox.x1 % this._pageWidth, childBox.y1);
                break;
            case Clutter.Orientation.VERTICAL:
                childBox.set_origin(childBox.x1, childBox.y1 % this._pageHeight);
                break;
            }

            // Outside the icon boundaries
            if (x < childBox.x1 - halfHSpacing ||
                x > childBox.x2 + halfHSpacing ||
                y < childBox.y1 - halfVSpacing ||
                y > childBox.y2 + halfVSpacing)
                continue;

            let dragLocation;

            if (x < childBox.x1 + LEFT_DIVIDER_LEEWAY)
                dragLocation = DragLocation.START_EDGE;
            else if (x > childBox.x2 - RIGHT_DIVIDER_LEEWAY)
                dragLocation = DragLocation.END_EDGE;
            else
                dragLocation = DragLocation.ON_ICON;

            if (isRtl) {
                if (dragLocation === DragLocation.START_EDGE)
                    dragLocation = DragLocation.END_EDGE;
                else if (dragLocation === DragLocation.END_EDGE)
                    dragLocation = DragLocation.START_EDGE;
            }

            return [item, dragLocation];
        }

        return [null, DragLocation.EMPTY_SPACE];
    }

    get iconSize() {
        return this._iconSize;
    }

    get nPages() {
        return this._pages.length;
    }

    get orientation() {
        return this._orientation;
    }

    set orientation(v) {
        if (this._orientation === v)
            return;

        switch (v) {
        case Clutter.Orientation.VERTICAL:
            this.request_mode = Clutter.RequestMode.HEIGHT_FOR_WIDTH;
            break;
        case Clutter.Orientation.HORIZONTAL:
            this.request_mode = Clutter.RequestMode.WIDTH_FOR_HEIGHT;
            break;
        }

        this._orientation = v;
        this.notify('orientation');
    }

    get pageHeight() {
        return this._pageHeight;
    }

    get pageWidth() {
        return this._pageWidth;
    }
});

var IconGrid = GObject.registerClass({
    Signals: {
        'pages-changed': {},
    },
}, class IconGrid extends St.Viewport {
    _init(layoutParams = {}) {
        layoutParams = Params.parse(layoutParams, {
            allow_incomplete_pages: false,
            orientation: Clutter.Orientation.HORIZONTAL,
            columns_per_page: 6,
            rows_per_page: 4,
            page_halign: Clutter.ActorAlign.FILL,
            page_padding: new Clutter.Margin(),
            page_valign: Clutter.ActorAlign.FILL,
            last_row_align: Clutter.ActorAlign.START,
            column_spacing: 0,
            row_spacing: 0,
        });
        const layoutManager = new IconGridLayout(layoutParams);
        const pagesChangedId = layoutManager.connect('pages-changed',
            () => this.emit('pages-changed'));

        super._init({
            style_class: 'icon-grid',
            layoutManager,
            x_expand: true,
            y_expand: true,
        });

        this._gridModes = defaultGridModes;
        this._currentPage = 0;
        this._currentMode = -1;

        this.connect('actor-added', this._childAdded.bind(this));
        this.connect('actor-removed', this._childRemoved.bind(this));
        this.connect('destroy', () => layoutManager.disconnect(pagesChangedId));
    }

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

    _ensureItemIsVisible(item) {
        if (!this.contains(item))
            throw new Error(`${item} is not a child of IconGrid`);

        const itemPage = this.layout_manager.getItemPage(item);
        this.goToPage(itemPage);
    }

    _setGridMode(modeIndex) {
        if (this._currentMode === modeIndex)
            return;

        this._currentMode = modeIndex;

        if (modeIndex !== -1) {
            const newMode = this._gridModes[modeIndex];

            this.layout_manager.rows_per_page = newMode.rows;
            this.layout_manager.columns_per_page = newMode.columns;
        }
    }

    findBestModeForSize(width, height) {
        const { pagePadding } = this.layout_manager;
        width -= pagePadding.left + pagePadding.right;
        height -= pagePadding.top + pagePadding.bottom;

        const sizeRatio = width / height;
        let closestRatio = Infinity;
        let bestMode = -1;

        for (let modeIndex in this._gridModes) {
            const mode = this._gridModes[modeIndex];
            const modeRatio = mode.columns / mode.rows;

            if (Math.abs(sizeRatio - modeRatio) < Math.abs(sizeRatio - closestRatio)) {
                closestRatio = modeRatio;
                bestMode = modeIndex;
            }
        }

        this._setGridMode(bestMode);
    }

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

    vfunc_style_changed() {
        super.vfunc_style_changed();

        const node = this.get_theme_node();
        this.layout_manager.column_spacing = node.get_length('column-spacing');
        this.layout_manager.row_spacing = node.get_length('row-spacing');

        let [found, value] = node.lookup_length('max-column-spacing', false);
        this.layout_manager.max_column_spacing = found ? value : -1;

        [found, value] = node.lookup_length('max-row-spacing', false);
        this.layout_manager.max_row_spacing = found ? value : -1;

        const padding = new Clutter.Margin();
        ['top', 'right', 'bottom', 'left'].forEach(side => {
            padding[side] = node.get_length(`page-padding-${side}`);
        });
        this.layout_manager.page_padding = padding;
    }

    /**
     * addItem:
     * @param {Clutter.Actor} item: item to append to the grid
     * @param {int} page: page number
     * @param {int} index: position in the page
     *
     * Adds @item to the grid. @item must not be part of the grid.
     *
     * If @index exceeds the number of items per page, @item will
     * be added to the next page.
     *
     * @page must be a number between 0 and the number of pages.
     * Adding to the page after next will create a new page.
     */
    addItem(item, page = -1, index = -1) {
        if (!(item.icon instanceof BaseIcon))
            throw new Error('Only items with a BaseIcon icon property can be added to IconGrid');

        this.layout_manager.addItem(item, page, index);
    }

    /**
     * appendItem:
     * @param {Clutter.Actor} item: item to append to the grid
     *
     * Appends @item to the grid. @item must not be part of the grid.
     */
    appendItem(item) {
        this.layout_manager.appendItem(item);
    }

    /**
     * moveItem:
     * @param {Clutter.Actor} item: item to move
     * @param {int} newPage: new page of the item
     * @param {int} newPosition: new page of the item
     *
     * Moves @item to the grid. @item must be part of the grid.
     */
    moveItem(item, newPage, newPosition) {
        this.layout_manager.moveItem(item, newPage, newPosition);
        this.queue_relayout();
    }

    /**
     * removeItem:
     * @param {Clutter.Actor} item: item to remove from the grid
     *
     * Removes @item to the grid. @item must be part of the grid.
     */
    removeItem(item) {
        if (!this.contains(item))
            throw new Error(`Item ${item} is not part of the IconGrid`);

        this.layout_manager.removeItem(item);
    }

    /**
     * goToPage:
     * @param {int} pageIndex: page index
     * @param {boolean} animate: animate the page transition
     *
     * Moves the current page to @pageIndex. @pageIndex must be a valid page
     * number.
     */
    goToPage(pageIndex, animate = true) {
        if (pageIndex >= this.nPages)
            throw new Error(`IconGrid does not have page ${pageIndex}`);

        let newValue;
        let adjustment;
        switch (this.layout_manager.orientation) {
        case Clutter.Orientation.VERTICAL:
            adjustment = this.vadjustment;
            newValue = pageIndex * this.layout_manager.pageHeight;
            break;
        case Clutter.Orientation.HORIZONTAL:
            adjustment = this.hadjustment;
            newValue = pageIndex * this.layout_manager.pageWidth;
            break;
        }

        this._currentPage = pageIndex;

        if (!this.mapped)
            animate = false;

        adjustment.ease(newValue, {
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            duration: animate ? PAGE_SWITCH_TIME : 0,
        });
    }

    /**
     * getItemPage:
     * @param {BaseIcon} item: the item
     *
     * Retrieves the page @item is in, or -1 if @item is not part of the grid.
     *
     * @returns {int} the page where @item is in
     */
    getItemPage(item) {
        return this.layout_manager.getItemPage(item);
    }

    /**
     * getItemPosition:
     * @param {BaseIcon} item: the item
     *
     * Retrieves the position of @item is its page, or -1 if @item is not
     * part of the grid.
     *
     * @returns {[int, int]} the page and position of @item
     */
    getItemPosition(item) {
        if (!this.contains(item))
            return [-1, -1];

        const layoutManager = this.layout_manager;
        return layoutManager.getItemPosition(item);
    }

    /**
     * getItemAt:
     * @param {int} page: the page
     * @param {int} position: the position in page
     *
     * Retrieves the item at @page and @position.
     *
     * @returns {BaseItem} the item at @page and @position, or null
     */
    getItemAt(page, position) {
        const layoutManager = this.layout_manager;
        return layoutManager.getItemAt(page, position);
    }

    /**
     * getItemsAtPage:
     * @param {int} page: the page index
     *
     * Retrieves the children at page @page, including invisible children.
     *
     * @returns {Array} an array of {Clutter.Actor}s
     */
    getItemsAtPage(page) {
        if (page < 0 || page > this.nPages)
            throw new Error(`Page ${page} does not exist at IconGrid`);

        const layoutManager = this.layout_manager;
        return layoutManager.getItemsAtPage(page);
    }

    get currentPage() {
        return this._currentPage;
    }

    set currentPage(v) {
        this.goToPage(v);
    }

    get nPages() {
        return this.layout_manager.nPages;
    }

    adaptToSize(width, height) {
        this.layout_manager.adaptToSize(width, height);
    }

    setGridModes(modes) {
        this._gridModes = modes ? modes : defaultGridModes;
        this.queue_relayout();
    }

    getDropTarget(x, y) {
        const layoutManager = this.layout_manager;
        return layoutManager.getDropTarget(x, y, this._currentPage);
    }

    get itemsPerPage() {
        const layoutManager = this.layout_manager;
        return layoutManager.rows_per_page * layoutManager.columns_per_page;
    }
});
(uuay)misc/G�}{/s8QD(xI�V"�N�batch.jsp// -*- 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 ease() 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 { GObject } = imports.gi;
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 GObject.NotImplementedError(`process in ${this.constructor.name}`);
    }

    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();
            });
        } else {
            // This task finished, process the next one
            this.nextTask();
        }
    }
};
Signals.addSignalMethods(ConsecutiveBatch.prototype);
(uuay)permissionStore.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PermissionStore */

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)networkAgent.jsry// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */

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

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;

Gio._promisify(Shell.NetworkAgent.prototype, 'init_async');
Gio._promisify(Shell.NetworkAgent.prototype, 'search_vpn_plugin');

const VPN_UI_GROUP = 'VPN Plugin UI';

var NetworkSecretDialog = GObject.registerClass(
class NetworkSecretDialog extends ModalDialog.ModalDialog {
    _init(agent, requestId, connection, settingName, hints, flags, contentOverride) {
        super._init({ 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 contentBox = new Dialog.MessageDialogContent({
            title: this._content.title,
            description: this._content.message,
        });

        let initialFocusSet = false;
        for (let i = 0; i < this._content.secrets.length; i++) {
            let secret = this._content.secrets[i];
            let reactive = secret.key != null;

            let entryParams = {
                style_class: 'prompt-dialog-password-entry',
                hint_text: secret.label,
                text: secret.value,
                can_focus: reactive,
                reactive,
                x_align: Clutter.ActorAlign.CENTER,
            };
            if (secret.password)
                secret.entry = new St.PasswordEntry(entryParams);
            else
                secret.entry = new St.Entry(entryParams);
            ShellEntry.addContextMenu(secret.entry);
            contentBox.add_child(secret.entry);

            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 (this._content.secrets.some(s => s.password)) {
            let capsLockWarning = new ShellEntry.CapsLockWarning();
            contentBox.add_child(capsLockWarning);
        }

        if (flags & NM.SecretAgentGetSecretsFlags.WPS_PBC_ACTIVE) {
            let descriptionLabel = new St.Label({
                text: _('Alternatively you can connect by pushing the “WPS” button on your router.'),
                style_class: 'message-dialog-description',
            });
            descriptionLabel.clutter_text.line_wrap = true;
            descriptionLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;

            contentBox.add_child(descriptionLabel);
        }

        this.contentLayout.add_child(contentBox);

        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 &&= 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 &&= secret.valid;
            if (secret.key !== null) {
                if (this._settingName === 'vpn')
                    this._agent.add_vpn_secret(this._requestId, secret.key, secret.value);
                else
                    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();

        /* 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');
            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.includes('pin')) {
                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 = _('Authentication required');
            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();

        const 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,
                    () => {
                        try {
                            global.context.restore_rlimit_nofile();
                        } catch (err) {
                        }
                    });

            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.add_vpn_secret(this._requestId, this._previousLine, line);
                this._previousLine = undefined;
            }
        } else {
            this._previousLine = line;
        }
    }

    async _readStdoutOldStyle() {
        const [line, len_] =
            await this._dataStdout.read_line_async(GLib.PRIORITY_DEFAULT, null);

        if (line === null) {
            // end of file
            this._stdout.close(null);
            return;
        }

        const decoder = new TextDecoder();
        this._vpnChildProcessLineOldStyle(decoder.decode(line));

        // try to read more!
        this._readStdoutOldStyle();
    }

    async _readStdoutNewStyle() {
        const cnt =
            await this._dataStdout.fill_async(-1, GLib.PRIORITY_DEFAULT, null);

        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 = new GLib.Bytes(this._dataStdout.peek_buffer());
            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,
                        password: keyfile.get_boolean(groups[i], 'IsSecret'),
                    });
                } else {
                    if (!value.length) // Ignore empty secrets
                        continue;

                    this._agent.add_vpn_secret(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._native.connect('new-request', this._newRequest.bind(this));
        this._native.connect('cancel-request', this._cancelRequest.bind(this));

        this._initialized = false;
        this._initNative();
    }

    async _initNative() {
        try {
            await this._native.init_async(GLib.PRIORITY_DEFAULT, null);
            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');
            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.includes('pin')) {
                title = _("PIN code required");
                body = _("PIN code is needed for the mobile broadband device");
                break;
            }
            // fall through
        case 'cdma':
        case 'bluetooth':
            title = _('Authentication required');
            body = _("A password is required to connect to “%s”.").format(connectionSetting.get_id());
            break;
        case 'vpn':
            title = _("VPN 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.showNotification(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];
        }
    }

    async _vpnRequest(requestId, connection, hints, flags) {
        let vpnSetting = connection.get_setting_vpn();
        let serviceType = vpnSetting.service_type;

        let binary = await this._findAuthBinary(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;
    }

    async _findAuthBinary(serviceType) {
        let plugin;

        try {
            plugin = await this._native.search_vpn_plugin(serviceType);
        } catch (e) {
            logError(e);
            return null;
        }

        const fileName = plugin.get_auth_dialog();
        if (!GLib.file_test(fileName, GLib.FileTest.IS_EXECUTABLE)) {
            log(`VPN plugin at ${fileName} is not executable`);
            return null;
        }

        const prop = plugin.lookup_property('GNOME', 'supports-external-ui-mode');
        const trimmedProp = prop?.trim().toLowerCase() ?? '';

        return {
            fileName,
            supportsHints: plugin.supports_hints(),
            externalUIMode: ['true', 'yes', 'on', '1'].includes(trimmedProp),
        };
    }
};
var Component = NetworkAgent;
(uuay)boxpointer.js5X// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported BoxPointer */

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

const Main = imports.ui.main;

var PopupAnimation = {
    NONE:  0,
    SLIDE: 1 << 0,
    FADE:  1 << 1,
    FULL:  ~0,
};

var POPUP_ANIMATION_TIME = 150;

/**
 * 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.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.set_child_above_sibling(this.bin, this._border);
        this._sourceAlignment = 0.5;
        this._muteKeys = true;
        this._muteInput = true;

        this.connect('notify::visible', () => {
            if (this.visible)
                Meta.disable_unredirect_for_display(global.display);
            else
                Meta.enable_unredirect_for_display(global.display);
        });
    }

    vfunc_captured_event(event) {
        if (event.type() === Clutter.EventType.ENTER ||
            event.type() === Clutter.EventType.LEAVE)
            return Clutter.EVENT_PROPAGATE;

        let mute = event.type() === Clutter.EventType.KEY_PRESS ||
            event.type() === Clutter.EventType.KEY_RELEASE
            ? this._muteKeys : this._muteInput;

        if (mute)
            return Clutter.EVENT_STOP;

        return Clutter.EVENT_PROPAGATE;
    }

    get arrowSide() {
        return this._arrowSide;
    }

    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._muteKeys = false;
        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;
            }
        }

        this.ease({
            opacity: 255,
            translation_x: 0,
            translation_y: 0,
            duration: animationTime,
            mode: Clutter.AnimationMode.LINEAR,
            onComplete: () => {
                this._muteInput = false;
                if (onComplete)
                    onComplete();
            },
        });
    }

    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 = true;
        this._muteKeys = true;

        this.remove_all_transitions();
        this.ease({
            opacity: fade ? 0 : 255,
            translation_x: translationX,
            translation_y: translationY,
            duration: animationTime,
            mode: Clutter.AnimationMode.LINEAR,
            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) {
        if (this._sourceActor && this._sourceActor.mapped) {
            this._reposition(box);
            this._updateFlip(box);
        }

        this.set_allocation(box);

        let themeNode = this.get_theme_node();
        let borderWidth = themeNode.get_length('-arrow-border-width');
        let rise = themeNode.get_length('-arrow-rise');
        let childBox = new Clutter.ActorBox();
        let [availWidth, availHeight] = themeNode.get_content_box(box).get_size();

        childBox.x1 = 0;
        childBox.y1 = 0;
        childBox.x2 = availWidth;
        childBox.y2 = availHeight;
        this._border.allocate(childBox);

        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);
    }

    _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 [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);
        }

        const [hasColor, bgColor] =
            themeNode.lookup_color('-arrow-background-color', false);
        if (hasColor) {
            Clutter.cairo_set_source_color(cr, bgColor);
            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) {
        if (!this._sourceActor || sourceActor != this._sourceActor) {
            this._sourceActor?.disconnectObject(this);

            this._sourceActor = sourceActor;

            this._sourceActor?.connectObject('destroy',
                () => (this._sourceActor = null), this);
        }

        this._arrowAlignment = alignment;

        this.queue_relayout();
    }

    setSourceAlignment(alignment) {
        this._sourceAlignment = alignment;

        if (!this._sourceActor)
            return;

        this.setPosition(this._sourceActor, this._arrowAlignment);
    }

    _reposition(allocationBox) {
        let sourceActor = this._sourceActor;
        let alignment = this._arrowAlignment;
        let monitorIndex = Main.layoutManager.findIndexForActor(sourceActor);

        this._sourceExtents = sourceActor.get_transformed_extents();
        this._workArea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);

        // Position correctly relative to the sourceActor
        const sourceAllocation = sourceActor.get_allocation_box();
        const sourceContentBox = sourceActor instanceof St.Widget
            ? sourceActor.get_theme_node().get_content_box(sourceAllocation)
            : new Clutter.ActorBox({
                x2: sourceAllocation.get_width(),
                y2: sourceAllocation.get_height(),
            });
        let sourceTopLeft = this._sourceExtents.get_top_left();
        let sourceBottomRight = this._sourceExtents.get_bottom_right();
        let sourceCenterX = sourceTopLeft.x + sourceContentBox.x1 + (sourceContentBox.x2 - sourceContentBox.x1) * this._sourceAlignment;
        let sourceCenterY = sourceTopLeft.y + sourceContentBox.y1 + (sourceContentBox.y2 - sourceContentBox.y1) * this._sourceAlignment;
        let [, , 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 = sourceBottomRight.y + gap;
            break;
        case St.Side.BOTTOM:
            resY = sourceTopLeft.y - natHeight - gap;
            break;
        case St.Side.LEFT:
            resX = sourceBottomRight.x + gap;
            break;
        case St.Side.RIGHT:
            resX = sourceTopLeft.x - 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)
                    resY -= 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
        allocationBox.set_origin(Math.floor(x), 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 sourceTopLeft = this._sourceExtents.get_top_left();
        let sourceBottomRight = this._sourceExtents.get_bottom_right();
        let [, , boxWidth, boxHeight] = this.get_preferred_size();
        let workarea = this._workArea;

        switch (arrowSide) {
        case St.Side.TOP:
            if (sourceBottomRight.y + boxHeight > workarea.y + workarea.height &&
                boxHeight < sourceTopLeft.y - workarea.y)
                return St.Side.BOTTOM;
            break;
        case St.Side.BOTTOM:
            if (sourceTopLeft.y - boxHeight < workarea.y &&
                boxHeight < workarea.y + workarea.height - sourceBottomRight.y)
                return St.Side.TOP;
            break;
        case St.Side.LEFT:
            if (sourceBottomRight.x + boxWidth > workarea.x + workarea.width &&
                boxWidth < sourceTopLeft.x - workarea.x)
                return St.Side.RIGHT;
            break;
        case St.Side.RIGHT:
            if (sourceTopLeft.x - boxWidth < workarea.x &&
                boxWidth < workarea.x + workarea.width - sourceBottomRight.x)
                return St.Side.LEFT;
            break;
        }

        return arrowSide;
    }

    _updateFlip(allocationBox) {
        let arrowSide = this._calculateArrowSide(this._userArrowSide);
        if (this._arrowSide != arrowSide) {
            this._arrowSide = arrowSide;
            this._reposition(allocationBox);

            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)searchController.js+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SearchController */

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

const Main = imports.ui.main;
const Search = imports.ui.search;
const ShellEntry = imports.ui.shellEntry;

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 [];
    return searchString.split(/\s+/);
}

var SearchController = GObject.registerClass({
    Properties: {
        'search-active': GObject.ParamSpec.boolean(
            'search-active', 'search-active', 'search-active',
            GObject.ParamFlags.READABLE,
            false),
    },
}, class SearchController extends St.Widget {
    _init(searchEntry, showAppsButton) {
        super._init({
            name: 'searchController',
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
            visible: false,
        });

        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._searchResults = new Search.SearchResultsView();
        this.add_child(this._searchResults);
        Main.ctrlAltTabManager.addGroup(this._entry, _('Search'), 'edit-find-symbolic');

        // 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.add_actor(this._focusTrap);

        global.focus_manager.add_group(this._searchResults);

        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;
            }
        });
    }

    prepareToEnterOverview() {
        this.reset();
        this._setSearchActive(false);
    }

    vfunc_unmap() {
        this.reset();

        super.vfunc_unmap();
    }

    _setSearchActive(searchActive) {
        if (this._searchActive === searchActive)
            return;

        this._searchActive = searchActive;
        this.notify('search-active');
    }

    _onShowAppsButtonToggled() {
        this._setSearchActive(false);
    }

    _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 symbol = event.get_key_symbol();

        if (symbol === Clutter.KEY_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);
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _searchCancelled() {
        this._setSearchActive(false);

        // 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.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.KEY_Multi_key)
            return true;

        if (symbol === Clutter.KEY_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);
        this._text.event(event, false);
    }

    // the entry does not show the hint
    _isActivated() {
        return this._text.text === this._entry.get_text();
    }

    _onTextChanged() {
        let terms = getTermsForSearchString(this._entry.get_text());

        const searchActive = terms.length > 0;
        this._searchResults.setTerms(terms);

        if (searchActive) {
            this._setSearchActive(true);

            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.KEY_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.KEY_Left;
                nextDirection = St.DirectionType.LEFT;
            } else {
                arrowNext = Clutter.KEY_Right;
                nextDirection = St.DirectionType.RIGHT;
            }

            if (symbol === Clutter.KEY_Tab) {
                this._searchResults.navigateFocus(St.DirectionType.TAB_FORWARD);
                return Clutter.EVENT_STOP;
            } else if (symbol === Clutter.KEY_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.KEY_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.KEY_Return || symbol === Clutter.KEY_KP_Enter) {
                this._searchResults.activateDefault();
                return Clutter.EVENT_STOP;
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onCapturedEvent(actor, event) {
        if (event.type() === Clutter.EventType.BUTTON_PRESS) {
            const targetActor = global.stage.get_event_actor(event);
            if (targetActor !== this._text &&
                this._text.has_key_focus() &&
                this._text.text === '' &&
                !this._text.has_preedit() &&
                !Main.layoutManager.keyboardBox.contains(targetActor)) {
                // 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;
    }

    get searchActive() {
        return this._searchActive;
    }
});
(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._initManagerProxy();
    }

    _tryToCompleteLoad() {
        if (this._numLoadInhibitors == 0)
            return;

        this._numLoadInhibitors--;
        if (this._numLoadInhibitors == 0) {
            if (this._onLoaded)
                this._onLoaded();
        }
    }

    async _addInterface(objectPath, interfaceName, onFinished) {
        let info = this._interfaceInfos[interfaceName];

        if (!info) {
            if (onFinished)
                onFinished();
            return;
        }

        const 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,
        });

        try {
            await proxy.init_async(GLib.PRIORITY_DEFAULT, this._cancellable);
        } 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);
        }
    }

    async _initManagerProxy() {
        try {
            await this._managerProxy.init_async(
                GLib.PRIORITY_DEFAULT, this._cancellable);
        } 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)keyboard.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported InputSourceIndicator */

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;

var INPUT_SOURCE_TYPE_XKB = 'xkb';
var INPUT_SOURCE_TYPE_IBUS = 'ibus';

var LayoutMenuItem = GObject.registerClass(
class LayoutMenuItem extends PopupMenu.PopupBaseMenuItem {
    _init(displayName, shortName) {
        super._init();

        this.label = new St.Label({
            text: displayName,
            x_expand: true,
        });
        this.indicator = new St.Label({ text: shortName });
        this.add_child(this.label);
        this.add(this.indicator);
        this.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.KEY_Left)
            this._select(this._previous());
        else if (keysym == Clutter.KEY_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,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });
        bin.set_child(symbol);
        box.add_child(bin);

        let text = new St.Label({
            text: item.displayName,
            x_align: Clutter.ActorAlign.CENTER,
        });
        box.add_child(text);

        this.addItem(box, text);
    }
});

var InputSourceSettings = class {
    constructor() {
        if (this.constructor === InputSourceSettings)
            throw new TypeError(`Cannot instantiate abstract class ${this.constructor.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));
    }

    async _reload() {
        let props;
        try {
            const result = await 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);
            [props] = result.deep_unpack();
        } catch (e) {
            log(`Could not get properties from ${this._BUS_NAME}`);
            return;
        }

        const layouts = props['X11Layout'].unpack();
        const variants = props['X11Variant'].unpack();
        const 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 });
        }
        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, 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._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 of Object.keys(this._inputSources).sort((a, b) => a - b))
            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 = [];
        if (this._mruSources.length > 1) {
            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 = `${language} (${longName})`;
                    shortName = this._makeEngineShortName(engineDesc);
                }
            }

            if (exists)
                infosList.push({ type, id, displayName, 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, id, displayName, 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 ||
            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));
            Main.overview.connectObject(
                'showing', this._setPerWindowInputSource.bind(this),
                'hidden', this._setPerWindowInputSource.bind(this), this);
        } else if (!this._sourcesPerWindow && this._focusWindowNotifyId != 0) {
            global.display.disconnect(this._focusWindowNotifyId);
            this._focusWindowNotifyId = 0;
            Main.overview.disconnectObject(this);

            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;
    }

    get keyboardManager() {
        return this._keyboardManager;
    }
};
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) {
        this.set_allocation(box);

        // 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);
        });
    }
});

var InputSourceIndicator = GObject.registerClass(
class InputSourceIndicator extends PanelMenu.Button {
    _init() {
        super._init(0.5, _("Keyboard"));

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

        this._menuItems = {};
        this._indicatorLabels = {};

        this._container = new InputSourceIndicatorContainer({ style_class: 'system-status-icon' });
        this.add_child(this._container);

        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._inputSourceManager.connectObject(
            'sources-changed', this._sourcesChanged.bind(this),
            'current-source-changed', this._currentSourceChanged.bind(this), this);
        this._inputSourceManager.reload();
    }

    _onDestroy() {
        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.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));

            const 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.hide();
            return;
        }

        this.show();

        this._buildPropSection(newSource.properties);

        this._menuItems[newSource.index].setOrnament(PopupMenu.Ornament.DOT);
        this._indicatorLabels[newSource.index].show();
    }

    _buildPropSection(properties) {
        this._propSeparator.hide();
        this._propSection.actor.hide();
        this._propSection.removeAll();

        this._buildPropSubMenu(this._propSection, properties);

        if (!this._propSection.isEmpty()) {
            this._propSection.actor.show();
            this._propSeparator.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 j = 0; j < group.length; ++j) {
                        if (group[j] == item) {
                            item.setOrnament(PopupMenu.Ornament.DOT);
                            item.prop.set_state(IBus.PropState.CHECKED);
                            ibusManager.activateProperty(item.prop.get_key(),
                                                         IBus.PropState.CHECKED);
                        } else {
                            group[j].setOrnament(PopupMenu.Ornament.NONE);
                            group[j].prop.set_state(IBus.PropState.UNCHECKED);
                            ibusManager.activateProperty(group[j].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 ${prop.get_key()} has invalid type ${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();
            }

            // The `default` layout from ibus engine means to
            // use the current keyboard layout.
            if (xkbLayout === 'default') {
                const current = this._inputSourceManager.keyboardManager.currentLayout;
                xkbLayout = current.layout;
                xkbVariant = current.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)jsParse.js�/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported getCompletions, getCommonPrefix, getDeclaredConstants */

// 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];

    return findTheBrace(expr, offset - 1, openBrace, closeBrace);
}

function findTheBrace(expr, offset, ...braces) {
    let [openBrace, closeBrace] = braces;

    if (offset < 0)
        return -1;

    if (expr.charAt(offset) == openBrace)
        return offset;

    if (expr.charAt(offset).match(/['"]/))
        return findTheBrace(expr, findMatchingQuote(expr, offset) - 1, ...braces);

    if (expr.charAt(offset) == '/')
        return findTheBrace(expr, findMatchingSlash(expr, offset) - 1, ...braces);

    if (expr.charAt(offset) == closeBrace)
        return findTheBrace(expr, findTheBrace(expr, offset - 1, ...braces) - 1, ...braces);

    return findTheBrace(expr, offset - 1, ...braces);
}

// 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 = '') {
    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;
}

// 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;
}

// Returns true if there is reason to think that eval(str)
// will modify the global scope
function isUnsafeExpression(str) {
    // 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)nightLight.js�
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Gio, GObject } = 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.Color';
const OBJECT_PATH = '/org/gnome/SettingsDaemon/Color';

const ColorInterface = loadInterfaceXML('org.gnome.SettingsDaemon.Color');
const ColorProxy = Gio.DBusProxy.makeProxyWrapper(ColorInterface);

var Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        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.visible = this._indicator.visible = visible;
    }
});
(uuay)signalTracker.js�/* exported TransientSignalHolder, addObjectSignalMethods */
const { GObject } = imports.gi;

const destroyableTypes = [];

/**
 * @private
 * @param {Object} obj - an object
 * @returns {bool} - true if obj has a 'destroy' GObject signal
 */
function _hasDestroySignal(obj) {
    return destroyableTypes.some(type => obj instanceof type);
}

var TransientSignalHolder = GObject.registerClass(
class TransientSignalHolder extends GObject.Object {
    static [GObject.signals] = {
        'destroy': {},
    };

    constructor(owner) {
        super();

        if (_hasDestroySignal(owner))
            owner.connectObject('destroy', () => this.destroy(), this);
    }

    destroy() {
        this.emit('destroy');
    }
});
registerDestroyableType(TransientSignalHolder);

class SignalManager {
    /**
     * @returns {SignalManager} - the SignalManager singleton
     */
    static getDefault() {
        if (!this._singleton)
            this._singleton = new SignalManager();
        return this._singleton;
    }

    constructor() {
        this._signalTrackers = new Map();
    }

    /**
     * @param {Object} obj - object to get signal tracker for
     * @returns {SignalTracker} - the signal tracker for object
     */
    getSignalTracker(obj) {
        let signalTracker = this._signalTrackers.get(obj);
        if (signalTracker === undefined) {
            signalTracker = new SignalTracker(obj);
            this._signalTrackers.set(obj, signalTracker);
        }
        return signalTracker;
    }

    /**
     * @param {Object} obj - object to get signal tracker for
     * @returns {?SignalTracker} - the signal tracker for object if it exists
     */
    maybeGetSignalTracker(obj) {
        return this._signalTrackers.get(obj) ?? null;
    }

    /*
     * @param {Object} obj - object to remove signal tracker for
     * @returns {void}
     */
    removeSignalTracker(obj) {
        this._signalTrackers.delete(obj);
    }
}

class SignalTracker {
    /**
     * @param {Object=} owner - object that owns the tracker
     */
    constructor(owner) {
        if (_hasDestroySignal(owner))
            this._ownerDestroyId = owner.connect_after('destroy', () => this.clear());

        this._owner = owner;
        this._map = new Map();
    }

    /**
     * @typedef SignalData
     * @property {number[]} ownerSignals - a list of handler IDs
     * @property {number} destroyId - destroy handler ID of tracked object
     */

    /**
     * @private
     * @param {Object} obj - a tracked object
     * @returns {SignalData} - signal data for object
     */
    _getSignalData(obj) {
        let data = this._map.get(obj);
        if (data === undefined) {
            data = { ownerSignals: [], destroyId: 0 };
            this._map.set(obj, data);
        }
        return data;
    }

    /**
     * @private
     * @param {GObject.Object} obj - tracked widget
     */
    _trackDestroy(obj) {
        const signalData = this._getSignalData(obj);
        if (signalData.destroyId)
            return;
        signalData.destroyId = obj.connect_after('destroy', () => this.untrack(obj));
    }

    _disconnectSignalForProto(proto, obj, id) {
        proto['disconnect'].call(obj, id);
    }

    _getObjectProto(obj) {
        return obj instanceof GObject.Object
            ? GObject.Object.prototype
            : Object.getPrototypeOf(obj);
    }

    _disconnectSignal(obj, id) {
        this._disconnectSignalForProto(this._getObjectProto(obj), obj, id);
    }

    _removeTracker() {
        if (this._ownerDestroyId)
            this._disconnectSignal(this._owner, this._ownerDestroyId);

        SignalManager.getDefault().removeSignalTracker(this._owner);

        delete this._ownerDestroyId;
        delete this._owner;
    }

    /**
     * @param {Object} obj - tracked object
     * @param {...number} handlerIds - tracked handler IDs
     * @returns {void}
     */
    track(obj, ...handlerIds) {
        if (_hasDestroySignal(obj))
            this._trackDestroy(obj);

        this._getSignalData(obj).ownerSignals.push(...handlerIds);
    }

    /**
     * @param {Object} obj - tracked object instance
     * @returns {void}
     */
    untrack(obj) {
        const { ownerSignals, destroyId } = this._getSignalData(obj);
        this._map.delete(obj);

        const ownerProto = this._getObjectProto(this._owner);
        ownerSignals.forEach(id =>
            this._disconnectSignalForProto(ownerProto, this._owner, id));
        if (destroyId)
            this._disconnectSignal(obj, destroyId);

        if (this._map.size === 0)
            this._removeTracker();
    }

    /**
     * @returns {void}
     */
    clear() {
        this._map.forEach((_, obj) => this.untrack(obj));
    }

    /**
     * @returns {void}
     */
    destroy() {
        this.clear();
        this._removeTracker();
    }
}

/**
 * Connect one or more signals, and associate the handlers
 * with a tracked object.
 *
 * All handlers for a particular object can be disconnected
 * by calling disconnectObject(). If object is a {Clutter.widget},
 * this is done automatically when the widget is destroyed.
 *
 * @param {object} thisObj - the emitter object
 * @param {...any} args - a sequence of signal-name/handler pairs
 * with an optional flags value, followed by an object to track
 * @returns {void}
 */
function connectObject(thisObj, ...args) {
    const getParams = argArray => {
        const [signalName, handler, arg, ...rest] = argArray;
        if (typeof arg !== 'number')
            return [signalName, handler, 0, arg, ...rest];

        const flags = arg;
        let flagsMask = 0;
        Object.values(GObject.ConnectFlags).forEach(v => (flagsMask |= v));
        if (!(flags & flagsMask))
            throw new Error(`Invalid flag value ${flags}`);
        if (flags & GObject.ConnectFlags.SWAPPED)
            throw new Error('Swapped signals are not supported');
        return [signalName, handler, flags, ...rest];
    };

    const connectSignal = (emitter, signalName, handler, flags) => {
        const isGObject = emitter instanceof GObject.Object;
        const func = (flags & GObject.ConnectFlags.AFTER) && isGObject
            ? 'connect_after'
            : 'connect';
        const emitterProto = isGObject
            ? GObject.Object.prototype
            : Object.getPrototypeOf(emitter);
        return emitterProto[func].call(emitter, signalName, handler);
    };

    const signalIds = [];
    while (args.length > 1) {
        const [signalName, handler, flags, ...rest] = getParams(args);
        signalIds.push(connectSignal(thisObj, signalName, handler, flags));
        args = rest;
    }

    const obj = args.at(0) ?? globalThis;
    const tracker = SignalManager.getDefault().getSignalTracker(thisObj);
    tracker.track(obj, ...signalIds);
}

/**
 * Disconnect all signals that were connected for
 * the specified tracked object
 *
 * @param {Object} thisObj - the emitter object
 * @param {Object} obj - the tracked object
 * @returns {void}
 */
function disconnectObject(thisObj, obj) {
    SignalManager.getDefault().maybeGetSignalTracker(thisObj)?.untrack(obj);
}

/**
 * Add connectObject()/disconnectObject() methods
 * to prototype. The prototype must have the connect()
 * and disconnect() signal methods.
 *
 * @param {prototype} proto - a prototype
 */
function addObjectSignalMethods(proto) {
    proto['connectObject'] = function (...args) {
        connectObject(this, ...args);
    };
    proto['connect_object'] = proto['connectObject'];

    proto['disconnectObject'] = function (obj) {
        disconnectObject(this, obj);
    };
    proto['disconnect_object'] = proto['disconnectObject'];
}

/**
 * Register a GObject type as having a 'destroy' signal
 * that should disconnect all handlers
 *
 * @param {GObject.Type} gtype - a GObject type
 */
function registerDestroyableType(gtype) {
    if (!GObject.type_is_a(gtype, GObject.Object))
        throw new Error(`${gtype} is not a GObject subclass`);

    if (!GObject.signal_lookup('destroy', gtype))
        throw new Error(`${gtype} does not have a destroy signal`);

    destroyableTypes.push(gtype);
}
(uuay)osdMonitorLabeler.jsJ
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported OsdMonitorLabeler */

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

const Main = imports.ui.main;

var OsdMonitorLabel = GObject.registerClass(
class OsdMonitorLabel extends St.Widget {
    _init(monitor, label) {
        super._init({ x_expand: true, y_expand: true });

        this._monitor = monitor;

        this._box = new St.BoxLayout({
            vertical: true,
        });
        this.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);
        Main.uiGroup.set_child_above_sibling(this, null);
        this._position();

        Meta.disable_unredirect_for_display(global.display);
        this.connect('destroy', () => {
            Meta.enable_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;
    }
});

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)altTab.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported AppSwitcherPopup, GroupCyclerPopup, WindowSwitcherPopup,
            WindowCyclerPopup */

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

const Main = imports.ui.main;
const SwitcherPopup = imports.ui.switcherPopup;

var APP_ICON_HOVER_TIMEOUT = 200; // milliseconds

var THUMBNAIL_DEFAULT_SIZE = 256;
var THUMBNAIL_POPUP_TIME = 500; // milliseconds
var THUMBNAIL_FADE_TIME = 100; // milliseconds

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();

        this._switcherList = new AppSwitcher(apps, this);
        this._items = this._switcherList.icons;
    }

    vfunc_allocate(box) {
        super.vfunc_allocate(box);

        // 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] = icon.get_transformed_position();
            let thumbnailCenter = posX + icon.width / 2;
            let [, 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 [, childNaturalHeight] = this._thumbnails.get_preferred_height(-1);
            childBox.y2 = childBox.y1 + childNaturalHeight;
            this._thumbnails.allocate(childBox);
        }
    }

    _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) {
        const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
        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.KEY_q || keysym === Clutter.KEY_Q) {
            this._quitApplication(this._selectedIndex);
        } else if (this._thumbnailsFocused) {
            if (keysym === Clutter.KEY_Left)
                this._select(this._selectedIndex, rtl ? this._nextWindow() : this._previousWindow());
            else if (keysym === Clutter.KEY_Right)
                this._select(this._selectedIndex, rtl ? this._previousWindow() : this._nextWindow());
            else if (keysym === Clutter.KEY_Up)
                this._select(this._selectedIndex, null, true);
            else if (keysym === Clutter.KEY_w || keysym === Clutter.KEY_W || keysym === Clutter.KEY_F4)
                this._closeAppWindow(this._selectedIndex, this._currentWindow);
            else
                return Clutter.EVENT_PROPAGATE;
        } else if (keysym == Clutter.KEY_Left) {
            this._select(rtl ? this._next() : this._previous());
        } else if (keysym == Clutter.KEY_Right) {
            this._select(rtl ? this._previous() : this._next());
        } else if (keysym == Clutter.KEY_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);
    }

    _windowActivated(thumbnailSwitcher, n) {
        let appIcon = this._items[this._selectedIndex];
        Main.activateWindow(appIcon.cachedWindows[n]);
        this.fadeAndDestroy();
    }

    _windowEntered(thumbnailSwitcher, n) {
        if (!this.mouseActive)
            return;

        this._select(this._selectedIndex, n);
    }

    _windowRemoved(thumbnailSwitcher, 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() {
        if (this._thumbnailTimeoutId != 0)
            GLib.source_remove(this._thumbnailTimeoutId);

        super._onDestroy();
    }

    /**
     * _select:
     * @param {number} app: index of the app to select
     * @param {number=} window: index of which of @app's windows to select
     * @param {bool} 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) {
            GLib.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 = GLib.timeout_add(
                GLib.PRIORITY_DEFAULT,
                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;
        this._thumbnails.ease({
            opacity: 0,
            duration: THUMBNAIL_FADE_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                thumbnailsActor.destroy();
                this.thumbnailsVisible = false;
            },
        });
        this._thumbnails = null;
        this._switcherList.removeAccessibleState(this._selectedIndex, Atk.StateType.EXPANDED);
    }

    _createThumbnails() {
        this._thumbnails = new ThumbnailSwitcher(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;
        this._thumbnails.ease({
            opacity: 255,
            duration: THUMBNAIL_FADE_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this.thumbnailsVisible = true;
            },
        });

        this._switcherList.addAccessibleState(this._selectedIndex, Atk.StateType.EXPANDED);
    }
});

var CyclerHighlight = GObject.registerClass(
class CyclerHighlight extends St.Widget {
    _init() {
        super._init({ layout_manager: new Clutter.BinLayout() });
        this._window = null;

        this._clone = new Clutter.Clone();
        this.add_actor(this._clone);

        this._highlight = new St.Widget({ style_class: 'cycler-highlight' });
        this.add_actor(this._highlight);

        let coordinate = Clutter.BindCoordinate.ALL;
        let constraint = new Clutter.BindConstraint({ coordinate });
        this._clone.bind_property('source', constraint, 'source', 0);

        this.add_constraint(constraint);

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

    set window(w) {
        if (this._window == w)
            return;

        this._window?.disconnectObject(this);

        this._window = w;

        if (this._clone.source)
            this._clone.source.sync_visibility();

        const windowActor = this._window?.get_compositor_private() ?? null;

        if (windowActor)
            windowActor.hide();

        this._clone.source = windowActor;

        if (this._window) {
            this._onSizeChanged();
            this._window.connectObject('size-changed',
                this._onSizeChanged.bind(this), this);
        } else {
            this._highlight.set_size(0, 0);
            this._highlight.hide();
        }
    }

    _onSizeChanged() {
        const bufferRect = this._window.get_buffer_rect();
        const rect = this._window.get_frame_rect();
        this._highlight.set_size(rect.width, rect.height);
        this._highlight.set_position(
            rect.x - bufferRect.x,
            rect.y - bufferRect.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({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
}, class CyclerPopup extends SwitcherPopup.SwitcherPopup {
    _init() {
        super._init();

        this._items = this._getWindows();

        this._highlight = new CyclerHighlight();
        global.window_group.add_actor(this._highlight);

        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, 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.destroy();

        super._onDestroy();
    }
});


var GroupCyclerPopup = GObject.registerClass(
class GroupCyclerPopup extends CyclerPopup {
    _init() {
        this._settings = new Gio.Settings({ schema_id: 'org.gnome.shell.app-switcher' });
        super._init();
    }

    _getWindows() {
        let app = Shell.WindowTracker.get_default().focus_app;
        let appWindows = app?.get_windows() ?? [];

        if (this._settings.get_boolean('current-workspace-only')) {
            const workspaceManager = global.workspace_manager;
            const workspace = workspaceManager.get_active_workspace();
            appWindows = appWindows.filter(
                window => window.located_on_workspace(workspace));
        }

        return appWindows;
    }

    _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();

        let mode = this._settings.get_enum('app-icon-mode');
        this._switcherList = new WindowSwitcher(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) {
        const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
        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.KEY_Left)
            this._select(rtl ? this._next() : this._previous());
        else if (keysym == Clutter.KEY_Right)
            this._select(rtl ? this._previous() : this._next());
        else if (keysym === Clutter.KEY_w || keysym === Clutter.KEY_W || keysym === Clutter.KEY_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();

        this.add_child(this._iconBin);
        this.label = new St.Label({
            text: this.app.get_name(),
            x_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this.label);
    }

    // eslint-disable-next-line camelcase
    set_size(size) {
        this.icon = this.app.create_icon_texture(size);
        this._iconBin.child = this.icon;
    }
});

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 = getWindows(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._altTabPopup = altTabPopup;
        this._delayedHighlighted = -1;
        this._mouseTimeOutId = 0;

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

    _onDestroy() {
        if (this._mouseTimeOutId != 0)
            GLib.source_remove(this._mouseTimeOutId);

        this.icons.forEach(
            icon => icon.app.disconnectObject(this));
    }

    _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();
        this._list.ensure_style();

        let iconPadding = themeNode.get_horizontal_padding();
        let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
        let [, labelNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
        let iconSpacing = labelNaturalHeight + iconPadding + iconBorder;
        let totalSpacing = this._list.spacing * (this._items.length - 1);

        // We just assume the whole screen here due to weirdness happening 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) {
        if (!this._iconSize)
            this._setIconSize();

        return super.vfunc_get_preferred_height(forWidth);
    }

    vfunc_allocate(box) {
        // Allocate the main list items
        super.vfunc_allocate(box);

        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);
        }
    }

    // We override SwitcherList's _onItemMotion method to delay
    // activation when the thumbnail list is open
    _onItemMotion(item) {
        if (item === this._items[this._highlighted] ||
            item === this._items[this._delayedHighlighted])
            return Clutter.EVENT_PROPAGATE;

        const index = this._items.indexOf(item);

        if (this._mouseTimeOutId !== 0) {
            GLib.source_remove(this._mouseTimeOutId);
            this._delayedHighlighted = -1;
            this._mouseTimeOutId = 0;
        }

        if (this._altTabPopup.thumbnailsVisible) {
            this._delayedHighlighted = index;
            this._mouseTimeOutId = GLib.timeout_add(
                GLib.PRIORITY_DEFAULT,
                APP_ICON_HOVER_TIMEOUT,
                () => {
                    this._enterItem(index);
                    this._delayedHighlighted = -1;
                    this._mouseTimeOutId = 0;
                    return GLib.SOURCE_REMOVE;
                });
            GLib.Source.set_name_by_id(this._mouseTimeOutId, '[gnome-shell] this._enterItem');
        } else {
            this._itemEntered(index);
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _enterItem(index) {
        let [x, y] = 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->ThumbnailSwitcher 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._highlighted]) {
            if (this.icons[this._highlighted].cachedWindows.length === 1)
                this._arrows[this._highlighted].hide();
            else
                this._arrows[this._highlighted].remove_style_pseudo_class('highlighted');
        }

        super.highlight(n, justOutline);

        if (this._highlighted !== -1) {
            if (justOutline && this.icons[this._highlighted].cachedWindows.length === 1)
                this._arrows[this._highlighted].show();
            else
                this._arrows[this._highlighted].add_style_pseudo_class('highlighted');
        }
    }

    _addIcon(appIcon) {
        this.icons.push(appIcon);
        let item = this.addItem(appIcon, appIcon.label);

        appIcon.app.connectObject('notify::state', app => {
            if (app.state != Shell.AppState.RUNNING)
                this._removeIcon(app);
        }, this);

        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._arrows[index].destroy();
        this._arrows.splice(index, 1);

        this.icons.splice(index, 1);
        this.removeItem(index);
    }
});

var ThumbnailSwitcher = GObject.registerClass(
class ThumbnailSwitcher extends SwitcherPopup.SwitcherList {
    _init(windows) {
        super._init(false);

        this._labels = [];
        this._thumbnailBins = [];
        this._clones = [];
        this._windows = windows;

        for (let i = 0; i < windows.length; i++) {
            const 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);

            const title = windows[i].get_title();
            const name = new St.Label({
                text: title,
                // St.Label doesn't support text-align
                x_align: Clutter.ActorAlign.CENTER,
            });
            this._labels.push(name);
            box.add_actor(name);

            this.addItem(box, name);
        }

        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 [, 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);

            mutterWindow.connectObject('destroy',
                source => this._removeThumbnail(source, clone), this);
            this._clones.push(clone);
        }

        // Make sure we only do this once
        this._thumbnailBins = [];
    }

    _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 => clone?.source.disconnectObject(this));
    }
});

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_child(this._icon);
        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 WindowSwitcher = GObject.registerClass(
class WindowSwitcher 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.window.connectObject('unmanaged',
                window => this._removeWindow(window), this);
        }

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

    _onDestroy() {
        this.icons.forEach(
            icon => icon.window.disconnectObject(this));
    }

    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) {
        let themeNode = this.get_theme_node();
        let contentBox = themeNode.get_content_box(box);
        const labelHeight = this._label.height;
        const totalLabelHeight =
            labelHeight + themeNode.get_padding(St.Side.BOTTOM);

        box.y2 -= totalLabelHeight;
        super.vfunc_allocate(box);

        // 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.
        box.y2 += totalLabelHeight;
        this.set_allocation(box);

        const childBox = new Clutter.ActorBox();
        childBox.x1 = contentBox.x1;
        childBox.x2 = contentBox.x2;
        childBox.y2 = contentBox.y2;
        childBox.y1 = childBox.y2 - labelHeight;
        this._label.allocate(childBox);
    }

    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)remoteAccess.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported RemoteAccessApplet, ScreenRecordingIndicator */

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

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

// Minimum amount of time the shared indicator is visible (in micro seconds)
const MIN_SHARED_INDICATOR_VISIBLE_TIME_US = 5 * GLib.TIME_SPAN_SECOND;

var RemoteAccessApplet = GObject.registerClass(
class RemoteAccessApplet extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        let controller = global.backend.get_remote_access_controller();

        if (!controller)
            return;

        this._handles = new Set();
        this._sharedIndicator = null;
        this._recordingIndicator = null;
        this._menuSection = null;

        controller.connect('new-handle', (o, handle) => {
            this._onNewHandle(handle);
        });
    }

    _ensureControls() {
        if (this._sharedIndicator && this._recordingIndicator)
            return;

        this._sharedIndicator = this._addIndicator();
        this._sharedIndicator.visible = false;
        this._sharedIndicator.icon_name = 'screen-shared-symbolic';
        this._sharedIndicator.add_style_class_name('remote-access-indicator');

        this._sharedItem =
            new PopupMenu.PopupSubMenuMenuItem(_("Screen is Being Shared"),
                                               true);
        this._sharedItem.menu.addAction(_("Turn off"),
            () => {
                for (let handle of this._handles) {
                    if (!handle.is_recording)
                        handle.stop();
                }
            });
        this._sharedItem.icon.icon_name = 'screen-shared-symbolic';
        this.menu.addMenuItem(this._sharedItem);

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

    _isScreenShared() {
        return [...this._handles].some(handle => !handle.is_recording);
    }

    _isRecording() {
        const recordingHandles =
            [...this._handles].filter(handle => handle.is_recording);

        // Screenshot UI screencasts have their own panel, so don't show this
        // indicator if there's only a screenshot UI screencast.
        if (Main.screenshotUI.screencast_in_progress)
            return recordingHandles.length > 1;

        return recordingHandles.length > 0;
    }

    _hideSharedIndicator() {
        this._sharedIndicator.visible = false;
        delete this._hideSharedIndicatorId;
        return GLib.SOURCE_REMOVE;
    }

    _sync() {
        if (this._hideSharedIndicatorId) {
            GLib.source_remove(this._hideSharedIndicatorId);
            delete this._hideSharedIndicatorId;
        }

        if (this._isScreenShared()) {
            if (!this._sharedIndicator.visible)
                this._visibleTimeUs = GLib.get_monotonic_time();
            this._sharedIndicator.visible = true;
            this._sharedItem.visible = true;
        } else {
            if (this._sharedIndicator.visible) {
                const currentTimeUs = GLib.get_monotonic_time();
                const timeSinceVisibleUs = currentTimeUs - this._visibleTimeUs;

                if (timeSinceVisibleUs >= MIN_SHARED_INDICATOR_VISIBLE_TIME_US) {
                    this._hideSharedIndicator();
                } else {
                    const timeUntilHideUs =
                        MIN_SHARED_INDICATOR_VISIBLE_TIME_US - timeSinceVisibleUs;
                    this._hideSharedIndicatorId =
                        GLib.timeout_add(
                            GLib.PRIORITY_DEFAULT,
                            timeUntilHideUs / GLib.TIME_SPAN_MILLISECOND,
                            this._hideSharedIndicator.bind(this));
                }
            }

            this._sharedItem.visible = false;
        }

        this._recordingIndicator.visible = this._isRecording();
    }

    _onStopped(handle) {
        this._handles.delete(handle);
        this._sync();
    }

    _onNewHandle(handle) {
        // 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.
        //
        // We still want to show screen recordings though, to indicate when
        // the built in screen recorder is active, no matter the session type.
        if (!Meta.is_wayland_compositor() && !handle.is_recording)
            return;

        this._handles.add(handle);
        handle.connect('stopped', this._onStopped.bind(this));

        this._ensureControls();
        this._sync();
    }
});

var ScreenRecordingIndicator = GObject.registerClass({
    Signals: { 'menu-set': {} },
}, class ScreenRecordingIndicator extends PanelMenu.ButtonBox {
    _init() {
        super._init({
            reactive: true,
            can_focus: true,
            track_hover: true,
            accessible_name: _('Stop Screencast'),
            accessible_role: Atk.Role.PUSH_BUTTON,
        });
        this.add_style_class_name('screen-recording-indicator');

        this._box = new St.BoxLayout();
        this.add_child(this._box);

        this._label = new St.Label({
            text: '0:00',
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._box.add_child(this._label);

        this._icon = new St.Icon({ icon_name: 'stop-symbolic' });
        this._box.add_child(this._icon);

        this.hide();
        Main.screenshotUI.connect(
            'notify::screencast-in-progress',
            this._onScreencastInProgressChanged.bind(this));
    }

    vfunc_event(event) {
        if (event.type() === Clutter.EventType.TOUCH_BEGIN ||
            event.type() === Clutter.EventType.BUTTON_PRESS)
            Main.screenshotUI.stopScreencast();

        return Clutter.EVENT_PROPAGATE;
    }

    _updateLabel() {
        const minutes = this._secondsPassed / 60;
        const seconds = this._secondsPassed % 60;
        this._label.text = '%d:%02d'.format(minutes, seconds);
    }

    _onScreencastInProgressChanged() {
        if (Main.screenshotUI.screencast_in_progress) {
            this.show();

            this._secondsPassed = 0;
            this._updateLabel();

            this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 1000, () => {
                this._secondsPassed += 1;
                this._updateLabel();
                return GLib.SOURCE_CONTINUE;
            });
            GLib.Source.set_name_by_id(
                this._timeoutId, '[gnome-shell] screen recording indicator tick');
        } else {
            this.hide();

            GLib.source_remove(this._timeoutId);
            delete this._timeoutId;

            delete this._secondsPassed;
        }
    }
});
(uuay)dateMenu.jsǂ// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported DateMenuButton */

const {
    Clutter, Gio, GLib, GnomeDesktop,
    GObject, GWeather, Pango, 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 { loadInterfaceXML } = imports.misc.fileUtils;

const NC_ = (context, str) => `${context}\u0004${str}`;
const T_ = Shell.util_translate_time_string;

const MAX_FORECASTS = 5;
const EN_CHAR = '\u2013';

const ClocksIntegrationIface = loadInterfaceXML('org.gnome.Shell.ClocksIntegration');
const ClocksProxy = Gio.DBusProxy.makeProxyWrapper(ClocksIntegrationIface);

function _isToday(date) {
    let now = new Date();
    return now.getYear() == date.getYear() &&
           now.getMonth() == date.getMonth() &&
           now.getDate() == date.getDate();
}

function _gDateTimeToDate(datetime) {
    return new Date(datetime.to_unix() * 1000 + datetime.get_microsecond() / 1000);
}

var TodayButton = GObject.registerClass(
class TodayButton extends St.Button {
    _init(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.
        super._init({
            style_class: 'datemenu-today-button',
            x_expand: true,
            can_focus: true,
            reactive: false,
        });

        let hbox = new St.BoxLayout({ vertical: true });
        this.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, datetime) => {
            // Make the button reactive only if the selected date is not the
            // current date.
            this.reactive = !_isToday(_gDateTimeToDate(datetime));
        });
    }

    vfunc_clicked() {
        this._calendar.setDate(new Date(), false);
    }

    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.accessible_name = date.toLocaleFormat(dateFormat);
    }
});

var EventsSection = GObject.registerClass(
class EventsSection extends St.Button {
    _init() {
        super._init({
            style_class: 'events-button',
            can_focus: true,
            x_expand: true,
            child: new St.BoxLayout({
                style_class: 'events-box',
                vertical: true,
                x_expand: true,
            }),
        });

        this._startDate = null;
        this._endDate = null;

        this._eventSource = null;
        this._calendarApp = null;

        this._title = new St.Label({
            style_class: 'events-title',
        });
        this.child.add_child(this._title);

        this._eventsList = new St.BoxLayout({
            style_class: 'events-list',
            vertical: true,
            x_expand: true,
        });
        this.child.add_child(this._eventsList);

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

    setDate(date) {
        this._startDate =
            new Date(date.getFullYear(), date.getMonth(), date.getDate());
        this._endDate =
            new Date(date.getFullYear(), date.getMonth(), date.getDate() + 1);

        this._updateTitle();
        this._reloadEvents();
    }

    setEventSource(eventSource) {
        if (!(eventSource instanceof Calendar.EventSourceBase))
            throw new Error('Event source is not valid type');

        this._eventSource = eventSource;
        this._eventSource.connect('changed', this._reloadEvents.bind(this));
        this._eventSource.connect('notify::has-calendars',
            this._sync.bind(this));
        this._sync();
    }

    _updateTitle() {
        /* Translators: Shown on calendar heading when selected day occurs on current year */
        const sameYearFormat = T_(NC_('calendar heading', '%B %-d'));

        /* Translators: Shown on calendar heading when selected day occurs on different year */
        const otherYearFormat = T_(NC_('calendar heading', '%B %-d %Y'));

        const timeSpanDay = GLib.TIME_SPAN_DAY / 1000;
        const now = new Date();

        if (this._startDate <= now && now < this._endDate)
            this._title.text = _('Today');
        else if (this._endDate <= now && now - this._endDate < timeSpanDay)
            this._title.text = _('Yesterday');
        else if (this._startDate > now && this._startDate - now <= timeSpanDay)
            this._title.text = _('Tomorrow');
        else if (this._startDate.getFullYear() === now.getFullYear())
            this._title.text = this._startDate.toLocaleFormat(sameYearFormat);
        else
            this._title.text = this._startDate.toLocaleFormat(otherYearFormat);
    }

    _isAtMidnight(eventTime) {
        return eventTime.getHours() === 0 && eventTime.getMinutes() === 0 && eventTime.getSeconds() === 0;
    }

    _formatEventTime(event) {
        const eventStart = event.date;
        let eventEnd = event.end;

        const allDay =
            eventStart.getTime() === this._startDate.getTime() && eventEnd.getTime() === this._endDate.getTime();

        const startsBeforeToday = eventStart < this._startDate;
        const endsAfterToday = eventEnd > this._endDate;

        const startTimeOnly = Util.formatTime(eventStart, { timeOnly: true });
        const endTimeOnly = Util.formatTime(eventEnd, { timeOnly: true });

        const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;

        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 if (startsBeforeToday || endsAfterToday) {
            const now = new Date();
            const thisYear = now.getFullYear();

            const startsAtMidnight = this._isAtMidnight(eventStart);
            const endsAtMidnight = this._isAtMidnight(eventEnd);

            const startYear = eventStart.getFullYear();

            if (endsAtMidnight) {
                eventEnd = new Date(eventEnd);
                eventEnd.setDate(eventEnd.getDate() - 1);
            }

            const endYear = eventEnd.getFullYear();

            let format;
            if (startYear === thisYear && thisYear === endYear)
                /* Translators: Shown in calendar event list as the start/end of events
                 * that only show day and month
                 */
                format = T_(N_('%m/%d'));
            else
                format = '%x';

            const startDateOnly = eventStart.toLocaleFormat(format);
            const endDateOnly = eventEnd.toLocaleFormat(format);

            if (startsAtMidnight && endsAtMidnight)
                title = `${rtl ? endDateOnly : startDateOnly} ${EN_CHAR} ${rtl ? startDateOnly : endDateOnly}`;
            else if (rtl)
                title = `${endTimeOnly} ${endDateOnly} ${EN_CHAR} ${startTimeOnly} ${startDateOnly}`;
            else
                title = `${startDateOnly} ${startTimeOnly} ${EN_CHAR} ${endDateOnly} ${endTimeOnly}`;
        } else if (eventStart === eventEnd) {
            title = startTimeOnly;
        } else {
            title = `${rtl ? endTimeOnly : startTimeOnly} ${EN_CHAR} ${rtl ? startTimeOnly : endTimeOnly}`;
        }

        return title;
    }

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

        this._reloading = true;

        [...this._eventsList].forEach(c => c.destroy());

        const events =
            this._eventSource.getEvents(this._startDate, this._endDate);

        for (let event of events) {
            const box = new St.BoxLayout({
                style_class: 'event-box',
                vertical: true,
            });
            box.add(new St.Label({
                text: event.summary,
                style_class: 'event-summary',
            }));
            box.add(new St.Label({
                text: this._formatEventTime(event),
                style_class: 'event-time',
            }));
            this._eventsList.add_child(box);
        }

        if (this._eventsList.get_n_children() === 0) {
            const placeholder = new St.Label({
                text: _('No Events'),
                style_class: 'event-placeholder',
            });
            this._eventsList.add_child(placeholder);
        }

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

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

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

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

        return this._sync();
    }

    _sync() {
        this.visible = this._eventSource && this._eventSource.hasCalendars;
        this.reactive = this._calendarApp !== null;
    }
});

var WorldClocksSection = GObject.registerClass(
class WorldClocksSection extends St.Button {
    _init() {
        super._init({
            style_class: 'world-clocks-button',
            can_focus: true,
            x_expand: true,
        });
        this._clock = new GnomeDesktop.WallClock();
        this._clockNotifyId = 0;
        this._tzNotifyId = 0;

        this._locations = [];

        let layout = new Clutter.GridLayout({ orientation: Clutter.Orientation.VERTICAL });
        this._grid = new St.Widget({
            style_class: 'world-clocks-grid',
            x_expand: true,
            layout_manager: layout,
        });
        layout.hookup_style(this._grid);

        this.child = this._grid;

        this._clocksApp = null;
        this._clocksProxy = new ClocksProxy(
            Gio.DBus.session,
            'org.gnome.clocks',
            '/org/gnome/clocks',
            this._onProxyReady.bind(this),
            null /* cancellable */,
            Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);

        this._settings = new Gio.Settings({
            schema_id: 'org.gnome.shell.world-clocks',
        });
        this._settings.connect('changed', this._clocksChanged.bind(this));
        this._clocksChanged();

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

    vfunc_clicked() {
        if (this._clocksApp)
            this._clocksApp.activate();

        Main.overview.hide();
        Main.panel.closeCalendar();
    }

    _sync() {
        this._clocksApp = this._appSystem.lookup_app('org.gnome.clocks.desktop');
        this.visible = this._clocksApp != null;
    }

    _clocksChanged() {
        this._grid.destroy_all_children();
        this._locations = [];

        let world = GWeather.Location.get_world();
        let clocks = this._settings.get_value('locations').deep_unpack();
        for (let i = 0; i < clocks.length; i++) {
            let l = world.deserialize(clocks[i]);
            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");
        const header = new St.Label({
            style_class: 'world-clocks-header',
            x_align: Clutter.ActorAlign.START,
            text: title,
        });
        if (this._grid.text_direction === Clutter.TextDirection.RTL)
            layout.attach(header, 2, 0, 1, 1);
        else
            layout.attach(header, 0, 0, 2, 1);
        this.label_actor = header;

        for (let i = 0; i < this._locations.length; i++) {
            let l = this._locations[i].location;

            let name = l.get_city_name() || l.get_name();
            const 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',
                x_align: Clutter.ActorAlign.END,
            });

            const tz = new St.Label({
                style_class: 'world-clocks-timezone',
                x_align: Clutter.ActorAlign.END,
                y_align: Clutter.ActorAlign.CENTER,
            });

            time.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
            tz.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;

            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].timeLabel = time;
            this._locations[i].tzLabel = tz;
        }

        if (this._grid.get_n_children() > 1) {
            if (!this._clockNotifyId) {
                this._clockNotifyId =
                    this._clock.connect('notify::clock', this._updateTimeLabels.bind(this));
            }
            if (!this._tzNotifyId) {
                this._tzNotifyId =
                    this._clock.connect('notify::timezone', this._updateTimezoneLabels.bind(this));
            }
            this._updateTimeLabels();
            this._updateTimezoneLabels();
        } else {
            if (this._clockNotifyId)
                this._clock.disconnect(this._clockNotifyId);
            this._clockNotifyId = 0;

            if (this._tzNotifyId)
                this._clock.disconnect(this._tzNotifyId);
            this._tzNotifyId = 0;
        }
    }

    _getTimezoneOffsetAtLocation(location) {
        const localOffset = GLib.DateTime.new_now_local().get_utc_offset();
        const utcOffset = this._getTimeAtLocation(location).get_utc_offset();
        const offsetCurrentTz = utcOffset - localOffset;
        const offsetHours =
            Math.floor(Math.abs(offsetCurrentTz) / GLib.TIME_SPAN_HOUR);
        const offsetMinutes =
            (Math.abs(offsetCurrentTz) % GLib.TIME_SPAN_HOUR) /
            GLib.TIME_SPAN_MINUTE;

        const prefix = offsetCurrentTz >= 0 ? '+' : '-';
        const text = offsetMinutes === 0
            ? `${prefix}${offsetHours}`
            : `${prefix}${offsetHours}\u2236${offsetMinutes}`;
        return text;
    }

    _getTimeAtLocation(location) {
        let tz = GLib.TimeZone.new(location.get_timezone().get_tzid());
        return GLib.DateTime.new_now(tz);
    }

    _updateTimeLabels() {
        let differentLength = false;
        let lastLength;
        for (let i = 0; i < this._locations.length; i++) {
            let l = this._locations[i];
            let now = this._getTimeAtLocation(l.location);
            const text = Util.formatTime(now, { timeOnly: true });
            l.timeLabel.text = text;

            if (differentLength)
                continue;
            if (lastLength === undefined)
                lastLength = text.length;
            differentLength = lastLength !== text.length;
        }

        for (let i = 0; i < this._locations.length; i++) {
            this._locations[i].timeLabel.x_align = differentLength
                ? Clutter.ActorAlign.START
                : Clutter.ActorAlign.END;
        }
    }

    _updateTimezoneLabels() {
        for (let i = 0; i < this._locations.length; i++) {
            let l = this._locations[i];
            l.tzLabel.text = this._getTimezoneOffsetAtLocation(l.location);
        }
    }

    _onProxyReady(proxy, error) {
        if (error) {
            log(`Failed to create GNOME Clocks proxy: ${error}`);
            return;
        }

        this._clocksProxy.connect('g-properties-changed',
            this._onClocksPropertiesChanged.bind(this));
        this._onClocksPropertiesChanged();
    }

    _onClocksPropertiesChanged() {
        if (this._clocksProxy.g_name_owner == null)
            return;

        this._settings.set_value('locations',
            new GLib.Variant('av', this._clocksProxy.Locations));
    }
});

var WeatherSection = GObject.registerClass(
class WeatherSection extends St.Button {
    _init() {
        super._init({
            style_class: 'weather-button',
            can_focus: true,
            x_expand: true,
        });

        this._weatherClient = new Weather.WeatherClient();

        let box = new St.BoxLayout({
            style_class: 'weather-box',
            vertical: true,
            x_expand: true,
        });

        this.child = box;

        let titleBox = new St.BoxLayout({ style_class: 'weather-header-box' });
        this._titleLabel = new St.Label({
            style_class: 'weather-header',
            x_align: Clutter.ActorAlign.START,
            x_expand: true,
            y_align: Clutter.ActorAlign.END,
        });
        titleBox.add_child(this._titleLabel);
        box.add_child(titleBox);

        this._titleLocation = new St.Label({
            style_class: 'weather-header location',
            x_align: Clutter.ActorAlign.END,
            y_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();
    }

    vfunc_map() {
        this._weatherClient.update();
        super.vfunc_map();
    }

    vfunc_clicked() {
        this._weatherClient.activateApp();

        Main.overview.hide();
        Main.panel.closeCalendar();
    }

    _getInfos() {
        let forecasts = this._weatherClient.info.get_forecast_list();

        let now = GLib.DateTime.new_now_local();
        let current = GLib.DateTime.new_from_unix_local(0);
        let infos = [];
        for (let i = 0; i < forecasts.length; i++) {
            const [valid, timestamp] = forecasts[i].get_value_update();
            if (!valid || timestamp === 0)
                continue;  // 0 means 'never updated'

            const datetime = GLib.DateTime.new_from_unix_local(timestamp);
            if (now.difference(datetime) > 0)
                continue; // Ignore earlier forecasts

            if (datetime.difference(current) < GLib.TIME_SPAN_HOUR)
                continue; // Enforce a minimum interval of 1h

            if (infos.push(forecasts[i]) == MAX_FORECASTS)
                break; // Use a maximum of five forecasts

            current = datetime;
        }
        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 => {
            const [valid_, timestamp] = fc.get_value_update();
            let timeStr = Util.formatTime(new Date(timestamp * 1000), {
                timeOnly: true,
                ampm: false,
            });
            const [, tempValue] = fc.get_value_temp(GWeather.TemperatureUnit.DEFAULT);
            const tempPrefix = Math.round(tempValue) >= 0 ? ' ' : '';

            let time = new St.Label({
                style_class: 'weather-forecast-time',
                text: timeStr,
                x_align: Clutter.ActorAlign.CENTER,
            });
            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: `${tempPrefix}${Math.round(tempValue)}°`,
                x_align: Clutter.ActorAlign.CENTER,
            });

            temp.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
            time.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;

            layout.attach(time, col, 0, 1, 1);
            layout.attach(icon, col, 1, 1, 1);
            layout.attach(temp, 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);
    }

    _findBestLocationName(loc) {
        const locName = loc.get_name();

        if (loc.get_level() === GWeather.LocationLevel.CITY ||
            !loc.has_coords())
            return locName;

        const world = GWeather.Location.get_world();
        const city = world.find_nearest_city(...loc.get_coords());
        const cityName = city.get_name();

        return locName.includes(cityName) ? cityName : locName;
    }

    _updateForecasts() {
        this._forecastGrid.destroy_all_children();

        if (!this._weatherClient.hasLocation)
            return;

        const { info } = this._weatherClient;
        this._titleLocation.text = this._findBestLocationName(info.location);

        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.visible = this._weatherClient.available;

        if (!this.visible)
            return;

        if (this._weatherClient.hasLocation)
            this._titleLabel.text = _('Weather');
        else
            this._titleLabel.text = _('Select weather location…');

        this._forecastGrid.visible = this._weatherClient.hasLocation;
        this._titleLocation.visible = this._weatherClient.hasLocation;

        this._updateForecasts();
    }
});

var MessagesIndicator = GObject.registerClass(
class MessagesIndicator extends St.Icon {
    _init() {
        super._init({
            icon_size: 16,
            visible: false,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this._sources = [];
        this._count = 0;

        this._settings = new Gio.Settings({
            schema_id: 'org.gnome.desktop.notifications',
        });
        this._settings.connect('changed::show-banners', this._sync.bind(this));

        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));

        this._sync();

        this.connect('destroy', () => {
            this._settings.run_dispose();
            this._settings = null;
        });
    }

    _onSourceAdded(tray, source) {
        source.connect('notify::count', 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));
        this._count = count - Main.messageTray.queueCount;

        this._sync();
    }

    _sync() {
        let doNotDisturb = !this._settings.get_boolean('show-banners');
        this.icon_name = doNotDisturb
            ? 'notifications-disabled-symbolic'
            : 'message-indicator-symbolic';
        this.visible = doNotDisturb || this._count > 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) {
        super.vfunc_allocate(container, allocation);

        let [width, height] = allocation.get_size();
        this._savedWidth = [width, width];
        this._savedHeight = [height, height];
    }
});

var CalendarColumnLayout = GObject.registerClass(
class CalendarColumnLayout extends Clutter.BoxLayout {
    _init(actors) {
        super._init({ orientation: Clutter.Orientation.VERTICAL });
        this._colActors = actors;
    }

    vfunc_get_preferred_width(container, forHeight) {
        const actors =
            this._colActors.filter(a => a.get_parent() === container);
        if (actors.length === 0)
            return super.vfunc_get_preferred_width(container, forHeight);
        return actors.reduce(([minAcc, natAcc], child) => {
            const [min, nat] = child.get_preferred_width(forHeight);
            return [Math.max(minAcc, min), Math.max(natAcc, nat)];
        }, [0, 0]);
    }
});

var DateMenuButton = GObject.registerClass(
class DateMenuButton extends PanelMenu.Button {
    _init() {
        let hbox;

        super._init(0.5);

        this._clockDisplay = new St.Label({ style_class: 'clock' });
        this._clockDisplay.clutter_text.y_align = Clutter.ActorAlign.CENTER;
        this._clockDisplay.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;

        this._indicator = new MessagesIndicator();

        const indicatorPad = new St.Widget();
        this._indicator.bind_property('visible',
            indicatorPad, 'visible',
            GObject.BindingFlags.SYNC_CREATE);
        indicatorPad.add_constraint(new Clutter.BindConstraint({
            source: this._indicator,
            coordinate: Clutter.BindCoordinate.SIZE,
        }));

        let box = new St.BoxLayout({ style_class: 'clock-display-box' });
        box.add_actor(indicatorPad);
        box.add_actor(this._clockDisplay);
        box.add_actor(this._indicator);

        this.label_actor = this._clockDisplay;
        this.add_actor(box);
        this.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, datetime) => {
            let date = _gDateTimeToDate(datetime);
            layout.frozen = !_isToday(date);
            this._eventsItem.setDate(date);
        });
        this._date = new TodayButton(this._calendar);

        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._eventsItem.setDate(now);
            }
        });

        // Fill up the first column
        this._messageList = new Calendar.CalendarMessageList();
        hbox.add_child(this._messageList);

        // Fill up the second column
        const boxLayout = new CalendarColumnLayout([this._calendar, this._date]);
        const vbox = new St.Widget({
            style_class: 'datemenu-calendar-column',
            layout_manager: boxLayout,
        });
        boxLayout.hookup_style(vbox);
        hbox.add(vbox);

        vbox.add_actor(this._date);
        vbox.add_actor(this._calendar);

        this._displaysSection = new St.ScrollView({
            style_class: 'datemenu-displays-section vfade',
            x_expand: true,
            overlay_scrollbars: true,
        });
        this._displaysSection.set_policy(St.PolicyType.NEVER, St.PolicyType.EXTERNAL);
        vbox.add_actor(this._displaysSection);

        const displaysBox = new St.BoxLayout({
            vertical: true,
            x_expand: true,
            style_class: 'datemenu-displays-box',
        });
        this._displaysSection.add_actor(displaysBox);

        this._eventsItem = new EventsSection();
        displaysBox.add_child(this._eventsItem);

        this._clocksItem = new WorldClocksSection();
        displaysBox.add_child(this._clocksItem);

        this._weatherItem = new WeatherSection();
        displaysBox.add_child(this._weatherItem);

        // 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._eventsItem.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)shellEntry.js2// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported addContextMenu CapsLockWarning */

const { Clutter, 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;

var EntryMenu = class extends PopupMenu.PopupMenu {
    constructor(entry) {
        super(entry, 0, St.Side.TOP);

        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;

        if (entry instanceof St.PasswordEntry)
            this._makePasswordItem();

        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._entry.bind_property('show-peek-icon',
            this._passwordItem, 'visible',
            GObject.BindingFlags.SYNC_CREATE);
    }

    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() {
        if (!this._entry.password_visible)
            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() {
        this._entry.password_visible  = !this._entry.password_visible;
    }
};

function _setMenuAlignment(entry, stageX) {
    let [success, entryX] = 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] = 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 cursorPosition = entry.clutter_text.get_cursor_position();
    let [success, textX, textY_, lineHeight_] = entry.clutter_text.position_to_coords(cursorPosition);
    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, { actionMode: Shell.ActionMode.POPUP });

    entry.menu = new EntryMenu(entry);
    entry._menuManager = new PopupMenu.PopupMenuManager(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;

        let seat = Clutter.get_default_backend().get_default_seat();
        this._keymap = seat.get_keymap();

        this.connect('notify::mapped', () => {
            if (this.is_mapped()) {
                this._keymap.connectObject(
                    'state-changed', () => this._sync(true), this);
            } else {
                this._keymap.disconnectObject(this);
            }

            this._sync(false);
        });
    }

    _sync(animate) {
        let capsLockOn = this._keymap.get_caps_lock_state();

        this.remove_all_transitions();

        const { naturalHeightSet } = this;
        this.natural_height_set = false;
        let [, height] = this.get_preferred_height(-1);
        this.natural_height_set = naturalHeightSet;

        this.ease({
            height: capsLockOn ? height : 0,
            opacity: capsLockOn ? 255 : 0,
            duration: animate ? 200 : 0,
            onComplete: () => {
                if (capsLockOn)
                    this.height = -1;
            },
        });
    }
});
(uuay)environment.js�>// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init */

const Config = imports.misc.config;

imports.gi.versions.AccountsService = '1.0';
imports.gi.versions.Atk = '1.0';
imports.gi.versions.Atspi = '2.0';
imports.gi.versions.Clutter = Config.LIBMUTTER_API_VERSION;
imports.gi.versions.Cogl = Config.LIBMUTTER_API_VERSION;
imports.gi.versions.Gcr = '3';
imports.gi.versions.Gdk = '3.0';
imports.gi.versions.Gdm = '1.0';
imports.gi.versions.Geoclue = '2.0';
imports.gi.versions.Gio = '2.0';
imports.gi.versions.GDesktopEnums = '3.0';
imports.gi.versions.GdkPixbuf = '2.0';
imports.gi.versions.GnomeBluetooth = '3.0';
imports.gi.versions.GnomeDesktop = '3.0';
imports.gi.versions.Graphene = '1.0';
imports.gi.versions.Gtk = '3.0';
imports.gi.versions.GWeather = '3.0';
imports.gi.versions.IBus = '1.0';
imports.gi.versions.Malcontent = '0';
imports.gi.versions.NM = '1.0';
imports.gi.versions.NMA = '1.0';
imports.gi.versions.Pango = '1.0';
imports.gi.versions.Polkit = '1.0';
imports.gi.versions.PolkitAgent = '1.0';
imports.gi.versions.Rsvg = '2.0';
imports.gi.versions.Soup = '3.0';
imports.gi.versions.TelepathyGLib = '0.12';
imports.gi.versions.TelepathyLogger = '0.2';
imports.gi.versions.UPowerGlib = '1.0';

try {
    if (Config.HAVE_SOUP2)
        throw new Error('Soup3 support not enabled');
    const Soup_ = imports.gi.Soup;
} catch (e) {
    imports.gi.versions.Soup = '2.4';
    const { Soup } = imports.gi;
    _injectSoup3Compat(Soup);
}

const { Clutter, Gio, GLib, GObject, Meta, Polkit, Shell, St } = imports.gi;
const Gettext = imports.gettext;
const Signals = imports.signals;
const System = imports.system;
const SignalTracker = imports.misc.signalTracker;

Gio._promisify(Gio.DataInputStream.prototype, 'fill_async');
Gio._promisify(Gio.DataInputStream.prototype, 'read_line_async');
Gio._promisify(Gio.DBus, 'get');
Gio._promisify(Gio.DBusConnection.prototype, 'call');
Gio._promisify(Gio.DBusProxy, 'new');
Gio._promisify(Gio.DBusProxy.prototype, 'init_async');
Gio._promisify(Gio.DBusProxy.prototype, 'call_with_unix_fd_list');
Gio._promisify(Polkit.Permission, 'new');

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;
                }
            });
        };
    }
}

/**
 * Mimick the Soup 3 APIs we use when falling back to Soup 2.4
 *
 * @param {object} Soup 2.4 namespace
 * @returns {void}
 */
function _injectSoup3Compat(Soup) {
    Soup.StatusCode = Soup.KnownStatusCode;

    Soup.Message.new_from_encoded_form =
        function (method, uri, form) {
            const soupUri = new Soup.URI(uri);
            soupUri.set_query(form);
            return Soup.Message.new_from_uri(method, soupUri);
        };
    Soup.Message.prototype.set_request_body_from_bytes =
        function (contentType, bytes) {
            this.set_request(
                contentType,
                Soup.MemoryUse.COPY,
                new TextDecoder().decode(bytes.get_data()));
        };

    Soup.Session.prototype.send_and_read_async =
        function (message, prio, cancellable, callback) {
            this.queue_message(message, () => callback(this, message));
        };
    Soup.Session.prototype.send_and_read_finish =
        function (message) {
            if (message.status_code !== Soup.KnownStatusCode.OK)
                return null;

            return message.response_body.flatten().get_as_bytes();
        };
}

function _makeEaseCallback(params, cleanup) {
    let onComplete = params.onComplete;
    delete params.onComplete;

    let onStopped = params.onStopped;
    delete params.onStopped;

    return isFinished => {
        cleanup();

        if (onStopped)
            onStopped(isFinished);
        if (onComplete && isFinished)
            onComplete();
    };
}

function _getPropertyTarget(actor, propName) {
    if (!propName.startsWith('@'))
        return [actor, propName];

    let [type, name, prop] = propName.split('.');
    switch (type) {
    case '@layout':
        return [actor.layout_manager, name];
    case '@actions':
        return [actor.get_action(name), prop];
    case '@constraints':
        return [actor.get_constraint(name), prop];
    case '@content':
        return [actor.content, name];
    case '@effects':
        return [actor.get_effect(name), prop];
    }

    throw new Error(`Invalid property name ${propName}`);
}

function _easeActor(actor, params) {
    actor.save_easing_state();

    if (params.duration != undefined)
        actor.set_easing_duration(params.duration);
    delete params.duration;

    if (params.delay != undefined)
        actor.set_easing_delay(params.delay);
    delete params.delay;

    let repeatCount = 0;
    if (params.repeatCount != undefined)
        repeatCount = params.repeatCount;
    delete params.repeatCount;

    let autoReverse = false;
    if (params.autoReverse != undefined)
        autoReverse = params.autoReverse;
    delete params.autoReverse;

    // repeatCount doesn't include the initial iteration
    const numIterations = repeatCount + 1;
    // whether the transition should finish where it started
    const isReversed = autoReverse && numIterations % 2 === 0;

    if (params.mode != undefined)
        actor.set_easing_mode(params.mode);
    delete params.mode;

    const prepare = () => {
        Meta.disable_unredirect_for_display(global.display);
        global.begin_work();
    };
    const cleanup = () => {
        Meta.enable_unredirect_for_display(global.display);
        global.end_work();
    };
    let callback = _makeEaseCallback(params, cleanup);

    // cancel overwritten transitions
    let animatedProps = Object.keys(params).map(p => p.replace('_', '-', 'g'));
    animatedProps.forEach(p => actor.remove_transition(p));

    if (actor.get_easing_duration() > 0 || !isReversed)
        actor.set(params);
    actor.restore_easing_state();

    const transitions = animatedProps
        .map(p => actor.get_transition(p))
        .filter(t => t !== null);

    transitions.forEach(t => t.set({ repeatCount, autoReverse }));

    const [transition] = transitions;

    if (transition && transition.delay)
        transition.connect('started', () => prepare());
    else
        prepare();

    if (transition)
        transition.connect('stopped', (t, finished) => callback(finished));
    else
        callback(true);
}

function _easeActorProperty(actor, propName, target, params) {
    // Avoid pointless difference with ease()
    if (params.mode)
        params.progress_mode = params.mode;
    delete params.mode;

    if (params.duration)
        params.duration = adjustAnimationTime(params.duration);
    let duration = Math.floor(params.duration || 0);

    let repeatCount = 0;
    if (params.repeatCount != undefined)
        repeatCount = params.repeatCount;
    delete params.repeatCount;

    let autoReverse = false;
    if (params.autoReverse != undefined)
        autoReverse = params.autoReverse;
    delete params.autoReverse;

    // repeatCount doesn't include the initial iteration
    const numIterations = repeatCount + 1;
    // whether the transition should finish where it started
    const isReversed = autoReverse && numIterations % 2 === 0;

    // Copy Clutter's behavior for implicit animations, see
    // should_skip_implicit_transition()
    if (actor instanceof Clutter.Actor && !actor.mapped)
        duration = 0;

    const prepare = () => {
        Meta.disable_unredirect_for_display(global.display);
        global.begin_work();
    };
    const cleanup = () => {
        Meta.enable_unredirect_for_display(global.display);
        global.end_work();
    };
    let callback = _makeEaseCallback(params, cleanup);

    // cancel overwritten transition
    actor.remove_transition(propName);

    if (duration == 0) {
        let [obj, prop] = _getPropertyTarget(actor, propName);

        if (!isReversed)
            obj[prop] = target;

        prepare();
        callback(true);

        return;
    }

    let pspec = actor.find_property(propName);
    let transition = new Clutter.PropertyTransition(Object.assign({
        property_name: propName,
        interval: new Clutter.Interval({ value_type: pspec.value_type }),
        remove_on_complete: true,
        repeat_count: repeatCount,
        auto_reverse: autoReverse,
    }, params));
    actor.add_transition(propName, transition);

    transition.set_to(target);

    if (transition.delay)
        transition.connect('started', () => prepare());
    else
        prepare();

    transition.connect('stopped', (t, finished) => callback(finished));
}

function _loggingFunc(...args) {
    let fields = { 'MESSAGE': args.join(', ') };
    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
    globalThis.global = Shell.Global.get();

    globalThis.log = _loggingFunc;

    globalThis._ = Gettext.gettext;
    globalThis.C_ = Gettext.pgettext;
    globalThis.ngettext = Gettext.ngettext;
    globalThis.N_ = s => s;

    GObject.gtypeNameBasedOnJSPath = true;

    GObject.Object.prototype.connectObject = function (...args) {
        SignalTracker.connectObject(this, ...args);
    };
    GObject.Object.prototype.connect_object = function (...args) {
        SignalTracker.connectObject(this, ...args);
    };
    GObject.Object.prototype.disconnectObject = function (...args) {
        SignalTracker.disconnectObject(this, ...args);
    };
    GObject.Object.prototype.disconnect_object = function (...args) {
        SignalTracker.disconnectObject(this, ...args);
    };

    const _addSignalMethods = Signals.addSignalMethods;
    Signals.addSignalMethods = function (prototype) {
        _addSignalMethods(prototype);
        SignalTracker.addObjectSignalMethods(prototype);
    };

    SignalTracker.registerDestroyableType(Clutter.Actor);

    // Miscellaneous monkeypatching
    _patchContainerClass(St.BoxLayout);

    _patchLayoutClass(Clutter.GridLayout, {
        row_spacing: 'spacing-rows',
        column_spacing: 'spacing-columns',
    });
    _patchLayoutClass(Clutter.BoxLayout, { spacing: 'spacing' });

    let origSetEasingDuration = Clutter.Actor.prototype.set_easing_duration;
    Clutter.Actor.prototype.set_easing_duration = function (msecs) {
        origSetEasingDuration.call(this, adjustAnimationTime(msecs));
    };
    let origSetEasingDelay = Clutter.Actor.prototype.set_easing_delay;
    Clutter.Actor.prototype.set_easing_delay = function (msecs) {
        origSetEasingDelay.call(this, adjustAnimationTime(msecs));
    };

    Clutter.Actor.prototype.ease = function (props) {
        _easeActor(this, props);
    };
    Clutter.Actor.prototype.ease_property = function (propName, target, params) {
        _easeActorProperty(this, propName, target, params);
    };
    St.Adjustment.prototype.ease = function (target, params) {
        // we're not an actor of course, but we implement the same
        // transition API as Clutter.Actor, so this works anyway
        _easeActorProperty(this, 'value', target, params);
    };

    Clutter.Actor.prototype[Symbol.iterator] = function* () {
        for (let c = this.get_first_child(); c; c = c.get_next_sibling())
            yield c;
    };

    Clutter.Actor.prototype.toString = function () {
        return St.describe_actor(this);
    };
    // Deprecation warning for former JS classes turned into an actor subclass
    Object.defineProperty(Clutter.Actor.prototype, 'actor', {
        get() {
            let klass = this.constructor.name;
            let { stack } = new Error();
            log(`Usage of object.actor is deprecated for ${klass}\n${stack}`);
            return this;
        },
    });

    Gio.File.prototype.touch_async = function (callback) {
        Shell.util_touch_file_async(this, callback);
    };
    Gio.File.prototype.touch_finish = function (result) {
        return Shell.util_touch_file_finish(this, result);
    };

    St.set_slow_down_factor = function (factor) {
        let { stack } = new Error();
        log(`St.set_slow_down_factor() is deprecated, use St.Settings.slow_down_factor\n${stack}`);
        St.Settings.get().slow_down_factor = factor;
    };

    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?.format(format) ?? '';
    };

    let slowdownEnv = GLib.getenv('GNOME_SHELL_SLOWDOWN_FACTOR');
    if (slowdownEnv) {
        let factor = parseFloat(slowdownEnv);
        if (!isNaN(factor) && factor > 0.0)
            St.Settings.get().slow_down_factor = factor;
    }

    // OK, now things are initialized enough that we can import shell JS
    const Format = imports.format;

    String.prototype.format = Format.format;

    Math.clamp = function (x, lower, upper) {
        return Math.min(Math.max(x, lower), upper);
    };
}

// adjustAnimationTime:
// @msecs: time in milliseconds
//
// Adjust @msecs to account for St's enable-animations
// and slow-down-factor settings
function adjustAnimationTime(msecs) {
    let settings = St.Settings.get();

    if (!settings.enable_animations)
        return Math.min(msecs, 1);
    return settings.slow_down_factor * msecs;
}

(uuay)org/CworkspaceAnimation.js@// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WorkspaceAnimationController, WorkspaceGroup */

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

const Background = imports.ui.background;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const SwipeTracker = imports.ui.swipeTracker;

const WINDOW_ANIMATION_TIME = 250;
const WORKSPACE_SPACING = 100;

var WorkspaceGroup = GObject.registerClass(
class WorkspaceGroup extends Clutter.Actor {
    _init(workspace, monitor, movingWindow) {
        super._init();

        this._workspace = workspace;
        this._monitor = monitor;
        this._movingWindow = movingWindow;
        this._windowRecords = [];

        if (this._workspace) {
            this._background = new Meta.BackgroundGroup();

            this.add_actor(this._background);

            this._bgManager = new Background.BackgroundManager({
                container: this._background,
                monitorIndex: this._monitor.index,
                controlPosition: false,
            });
        }

        this.width = monitor.width;
        this.height = monitor.height;
        this.clip_to_allocation = true;

        this._createWindows();

        this.connect('destroy', this._onDestroy.bind(this));
        global.display.connectObject('restacked',
            this._syncStacking.bind(this), this);
    }

    get workspace() {
        return this._workspace;
    }

    _shouldShowWindow(window) {
        if (!window.showing_on_its_workspace())
            return false;

        const geometry = global.display.get_monitor_geometry(this._monitor.index);
        const [intersects] = window.get_frame_rect().intersect(geometry);
        if (!intersects)
            return false;

        const isSticky =
            window.is_on_all_workspaces() || window === this._movingWindow;

        // No workspace means we should show windows that are on all workspaces
        if (!this._workspace)
            return isSticky;

        // Otherwise only show windows that are (only) on that workspace
        return !isSticky && window.located_on_workspace(this._workspace);
    }

    _syncStacking() {
        const windowActors = global.get_window_actors().filter(w =>
            this._shouldShowWindow(w.meta_window));

        let lastRecord;
        const bottomActor = this._background ?? null;

        for (const windowActor of windowActors) {
            const record = this._windowRecords.find(r => r.windowActor === windowActor);

            this.set_child_above_sibling(record.clone,
                lastRecord ? lastRecord.clone : bottomActor);
            lastRecord = record;
        }
    }

    _createWindows() {
        const windowActors = global.get_window_actors().filter(w =>
            this._shouldShowWindow(w.meta_window));

        for (const windowActor of windowActors) {
            const clone = new Clutter.Clone({
                source: windowActor,
                x: windowActor.x - this._monitor.x,
                y: windowActor.y - this._monitor.y,
            });

            this.add_child(clone);

            const record = { windowActor, clone };

            windowActor.connectObject('destroy', () => {
                clone.destroy();
                this._windowRecords.splice(this._windowRecords.indexOf(record), 1);
            }, this);

            this._windowRecords.push(record);
        }
    }

    _removeWindows() {
        for (const record of this._windowRecords)
            record.clone.destroy();

        this._windowRecords = [];
    }

    _onDestroy() {
        this._removeWindows();

        if (this._workspace)
            this._bgManager.destroy();
    }
});

const MonitorGroup = GObject.registerClass({
    Properties: {
        'progress': GObject.ParamSpec.double(
            'progress', 'progress', 'progress',
            GObject.ParamFlags.READWRITE,
            -Infinity, Infinity, 0),
    },
}, class MonitorGroup extends St.Widget {
    _init(monitor, workspaceIndices, movingWindow) {
        super._init({
            clip_to_allocation: true,
            style_class: 'workspace-animation',
        });

        this._monitor = monitor;

        const constraint = new Layout.MonitorConstraint({ index: monitor.index });
        this.add_constraint(constraint);

        this._container = new Clutter.Actor();
        this.add_child(this._container);

        const stickyGroup = new WorkspaceGroup(null, monitor, movingWindow);
        this.add_child(stickyGroup);

        this._workspaceGroups = [];

        const workspaceManager = global.workspace_manager;
        const vertical = workspaceManager.layout_rows === -1;
        const activeWorkspace = workspaceManager.get_active_workspace();

        let x = 0;
        let y = 0;

        for (const i of workspaceIndices) {
            const ws = workspaceManager.get_workspace_by_index(i);
            const fullscreen = ws.list_windows().some(w => w.get_monitor() === monitor.index && w.is_fullscreen());

            if (i > 0 && vertical && !fullscreen && monitor.index === Main.layoutManager.primaryIndex) {
                // We have to shift windows up or down by the height of the panel to prevent having a
                // visible gap between the windows while switching workspaces. Since fullscreen windows
                // hide the panel, they don't need to be shifted up or down.
                y -= Main.panel.height;
            }

            const group = new WorkspaceGroup(ws, monitor, movingWindow);

            this._workspaceGroups.push(group);
            this._container.add_child(group);
            group.set_position(x, y);

            if (vertical)
                y += this.baseDistance;
            else if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
                x -= this.baseDistance;
            else
                x += this.baseDistance;
        }

        this.progress = this.getWorkspaceProgress(activeWorkspace);
    }

    get baseDistance() {
        const spacing = WORKSPACE_SPACING * St.ThemeContext.get_for_stage(global.stage).scale_factor;

        if (global.workspace_manager.layout_rows === -1)
            return this._monitor.height + spacing;
        else
            return this._monitor.width + spacing;
    }

    get progress() {
        if (global.workspace_manager.layout_rows === -1)
            return -this._container.y / this.baseDistance;
        else if (this.get_text_direction() === Clutter.TextDirection.RTL)
            return this._container.x / this.baseDistance;
        else
            return -this._container.x / this.baseDistance;
    }

    set progress(p) {
        if (global.workspace_manager.layout_rows === -1)
            this._container.y = -Math.round(p * this.baseDistance);
        else if (this.get_text_direction() === Clutter.TextDirection.RTL)
            this._container.x = Math.round(p * this.baseDistance);
        else
            this._container.x = -Math.round(p * this.baseDistance);
    }

    get index() {
        return this._monitor.index;
    }

    getWorkspaceProgress(workspace) {
        const group = this._workspaceGroups.find(g =>
            g.workspace.index() === workspace.index());
        return this._getWorkspaceGroupProgress(group);
    }

    _getWorkspaceGroupProgress(group) {
        if (global.workspace_manager.layout_rows === -1)
            return group.y / this.baseDistance;
        else if (this.get_text_direction() === Clutter.TextDirection.RTL)
            return -group.x / this.baseDistance;
        else
            return group.x / this.baseDistance;
    }

    getSnapPoints() {
        return this._workspaceGroups.map(g =>
            this._getWorkspaceGroupProgress(g));
    }

    findClosestWorkspace(progress) {
        const distances = this.getSnapPoints().map(p =>
            Math.abs(p - progress));
        const index = distances.indexOf(Math.min(...distances));
        return this._workspaceGroups[index].workspace;
    }

    _interpolateProgress(progress, monitorGroup) {
        if (this.index === monitorGroup.index)
            return progress;

        const points1 = monitorGroup.getSnapPoints();
        const points2 = this.getSnapPoints();

        const upper = points1.indexOf(points1.find(p => p >= progress));
        const lower = points1.indexOf(points1.slice().reverse().find(p => p <= progress));

        if (points1[upper] === points1[lower])
            return points2[upper];

        const t = (progress - points1[lower]) / (points1[upper] - points1[lower]);

        return points2[lower] + (points2[upper] - points2[lower]) * t;
    }

    updateSwipeForMonitor(progress, monitorGroup) {
        this.progress = this._interpolateProgress(progress, monitorGroup);
    }
});

var WorkspaceAnimationController = class {
    constructor() {
        this._movingWindow = null;
        this._switchData = null;

        Main.overview.connect('showing', () => {
            if (this._switchData) {
                if (this._switchData.gestureActivated)
                    this._finishWorkspaceSwitch(this._switchData);
                this._swipeTracker.enabled = false;
            }
        });
        Main.overview.connect('hiding', () => {
            this._swipeTracker.enabled = true;
        });

        const swipeTracker = new SwipeTracker.SwipeTracker(global.stage,
            Clutter.Orientation.HORIZONTAL,
            Shell.ActionMode.NORMAL,
            { allowDrag: false });
        swipeTracker.connect('begin', this._switchWorkspaceBegin.bind(this));
        swipeTracker.connect('update', this._switchWorkspaceUpdate.bind(this));
        swipeTracker.connect('end', this._switchWorkspaceEnd.bind(this));
        this._swipeTracker = swipeTracker;

        global.display.bind_property('compositor-modifiers',
            this._swipeTracker, 'scroll-modifiers',
            GObject.BindingFlags.SYNC_CREATE);
    }

    _prepareWorkspaceSwitch(workspaceIndices) {
        if (this._switchData)
            return;

        const workspaceManager = global.workspace_manager;
        const nWorkspaces = workspaceManager.get_n_workspaces();

        const switchData = {};

        this._switchData = switchData;
        switchData.monitors = [];

        switchData.gestureActivated = false;
        switchData.inProgress = false;

        if (!workspaceIndices)
            workspaceIndices = [...Array(nWorkspaces).keys()];

        const monitors = Meta.prefs_get_workspaces_only_on_primary()
            ? [Main.layoutManager.primaryMonitor] : Main.layoutManager.monitors;

        for (const monitor of monitors) {
            if (Meta.prefs_get_workspaces_only_on_primary() &&
                monitor.index !== Main.layoutManager.primaryIndex)
                continue;

            const group = new MonitorGroup(monitor, workspaceIndices, this.movingWindow);

            Main.uiGroup.insert_child_above(group, global.window_group);

            switchData.monitors.push(group);
        }

        Meta.disable_unredirect_for_display(global.display);
    }

    _finishWorkspaceSwitch(switchData) {
        Meta.enable_unredirect_for_display(global.display);

        this._switchData = null;

        switchData.monitors.forEach(m => m.destroy());

        this.movingWindow = null;
    }

    animateSwitch(from, to, direction, onComplete) {
        this._swipeTracker.enabled = false;

        let workspaceIndices = [];

        switch (direction) {
        case Meta.MotionDirection.UP:
        case Meta.MotionDirection.LEFT:
        case Meta.MotionDirection.UP_LEFT:
        case Meta.MotionDirection.UP_RIGHT:
            workspaceIndices = [to, from];
            break;

        case Meta.MotionDirection.DOWN:
        case Meta.MotionDirection.RIGHT:
        case Meta.MotionDirection.DOWN_LEFT:
        case Meta.MotionDirection.DOWN_RIGHT:
            workspaceIndices = [from, to];
            break;
        }

        if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL &&
            direction !== Meta.MotionDirection.UP &&
            direction !== Meta.MotionDirection.DOWN)
            workspaceIndices.reverse();

        this._prepareWorkspaceSwitch(workspaceIndices);
        this._switchData.inProgress = true;

        const fromWs = global.workspace_manager.get_workspace_by_index(from);
        const toWs = global.workspace_manager.get_workspace_by_index(to);

        for (const monitorGroup of this._switchData.monitors) {
            monitorGroup.progress = monitorGroup.getWorkspaceProgress(fromWs);
            const progress = monitorGroup.getWorkspaceProgress(toWs);

            const params = {
                duration: WINDOW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            };

            if (monitorGroup.index === Main.layoutManager.primaryIndex) {
                params.onComplete = () => {
                    this._finishWorkspaceSwitch(this._switchData);
                    onComplete();
                    this._swipeTracker.enabled = true;
                };
            }

            monitorGroup.ease_property('progress', progress, params);
        }
    }

    canHandleScrollEvent(event) {
        return this._swipeTracker.canHandleScrollEvent(event);
    }

    _findMonitorGroup(monitorIndex) {
        return this._switchData.monitors.find(m => m.index === monitorIndex);
    }

    _switchWorkspaceBegin(tracker, monitor) {
        if (Meta.prefs_get_workspaces_only_on_primary() &&
            monitor !== Main.layoutManager.primaryIndex)
            return;

        const workspaceManager = global.workspace_manager;
        const horiz = workspaceManager.layout_rows !== -1;
        tracker.orientation = horiz
            ? Clutter.Orientation.HORIZONTAL
            : Clutter.Orientation.VERTICAL;

        if (this._switchData && this._switchData.gestureActivated) {
            for (const group of this._switchData.monitors)
                group.remove_all_transitions();
        } else {
            this._prepareWorkspaceSwitch();
        }

        const monitorGroup = this._findMonitorGroup(monitor);
        const baseDistance = monitorGroup.baseDistance;
        const progress = monitorGroup.progress;

        const closestWs = monitorGroup.findClosestWorkspace(progress);
        const cancelProgress = monitorGroup.getWorkspaceProgress(closestWs);
        const points = monitorGroup.getSnapPoints();

        this._switchData.baseMonitorGroup = monitorGroup;

        tracker.confirmSwipe(baseDistance, points, progress, cancelProgress);
    }

    _switchWorkspaceUpdate(tracker, progress) {
        if (!this._switchData)
            return;

        for (const monitorGroup of this._switchData.monitors)
            monitorGroup.updateSwipeForMonitor(progress, this._switchData.baseMonitorGroup);
    }

    _switchWorkspaceEnd(tracker, duration, endProgress) {
        if (!this._switchData)
            return;

        const switchData = this._switchData;
        switchData.gestureActivated = true;

        const newWs = switchData.baseMonitorGroup.findClosestWorkspace(endProgress);
        const endTime = Clutter.get_current_event_time();

        for (const monitorGroup of this._switchData.monitors) {
            const progress = monitorGroup.getWorkspaceProgress(newWs);

            const params = {
                duration,
                mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            };

            if (monitorGroup.index === Main.layoutManager.primaryIndex) {
                params.onComplete = () => {
                    if (!newWs.active)
                        newWs.activate(endTime);
                    this._finishWorkspaceSwitch(switchData);
                };
            }

            monitorGroup.ease_property('progress', progress, params);
        }
    }

    get gestureActive() {
        return this._switchData !== null && this._switchData.gestureActivated;
    }

    cancelSwitchAnimation() {
        if (!this._switchData)
            return;

        if (this._switchData.gestureActivated)
            return;

        this._finishWorkspaceSwitch(this._switchData);
    }

    set movingWindow(movingWindow) {
        this._movingWindow = movingWindow;
    }

    get movingWindow() {
        return this._movingWindow;
    }
};
(uuay)panel.js�h// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Panel */

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

const Animation = imports.ui.animation;
const { AppMenu } = imports.ui.appMenu;
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;

var PANEL_ICON_SIZE = 16;
var APP_MENU_ICON_MARGIN = 0;

var BUTTON_DND_ACTIVATION_TIMEOUT = 250;

/**
 * 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.accessible_role = Atk.Role.MENU;

        this._startingApps = [];

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

        let bin = new St.Bin({ name: 'appMenu' });
        this.add_actor(bin);

        this.bind_property("reactive", this, "can-focus", 0);
        this.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',
            y_align: Clutter.ActorAlign.CENTER,
        });
        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._visible = !Main.overview.visible;
        if (!this._visible)
            this.hide();
        Main.overview.connectObject(
            'hiding', this._sync.bind(this),
            'showing', this._sync.bind(this), this);

        this._spinner = new Animation.Spinner(PANEL_ICON_SIZE, {
            animate: true,
            hideOnStop: true,
        });
        this._container.add_actor(this._spinner);

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

        Shell.WindowTracker.get_default().connectObject('notify::focus-app',
            this._focusAppChanged.bind(this), this);
        Shell.AppSystem.get_default().connectObject('app-state-changed',
            this._onAppStateChanged.bind(this), this);
        global.window_manager.connectObject('switch-workspace',
            this._sync.bind(this), this);

        this._sync();
    }

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

        this._visible = true;
        this.reactive = true;
        this.remove_all_transitions();
        this.ease({
            opacity: 255,
            duration: Overview.ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

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

        this._visible = false;
        this.reactive = false;
        this.remove_all_transitions();
        this.ease({
            opacity: 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            duration: Overview.ANIMATION_TIME,
        });
    }

    _syncIcon(app) {
        const icon = app.create_icon_texture(PANEL_ICON_SIZE - APP_MENU_ICON_MARGIN);
        this._iconBox.set_child(icon);
    }

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

        if (this._targetApp)
            this._syncIcon(this._targetApp);
    }

    stopAnimation() {
        this._spinner.stop();
    }

    startAnimation() {
        this._spinner.play();
    }

    _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) {
            this._targetApp?.disconnectObject(this);

            this._targetApp = targetApp;

            if (this._targetApp) {
                this._targetApp.connectObject('notify::busy', this._sync.bind(this), this);
                this._label.set_text(this._targetApp.get_name());
                this.set_accessible_name(this._targetApp.get_name());

                this._syncIcon(this._targetApp);
            }
        }

        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.reactive = visible && !isBusy;

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

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

        this.name = 'panelActivities';

        /* 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,
        });
        this.add_actor(this._label);

        this.label_actor = this._label;

        Main.overview.connect('showing', () => {
            this.add_style_pseudo_class('overview');
            this.add_accessible_state(Atk.StateType.CHECKED);
        });
        Main.overview.connect('hiding', () => {
            this.remove_style_pseudo_class('overview');
            this.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)
            GLib.source_remove(this._xdndTimeOut);
        this._xdndTimeOut = GLib.timeout_add(GLib.PRIORITY_DEFAULT, BUTTON_DND_ACTIVATION_TIMEOUT, () => {
            this._xdndToggleOverview();
        });
        GLib.Source.set_name_by_id(this._xdndTimeOut, '[gnome-shell] this._xdndToggleOverview');

        return DND.DragMotionResult.CONTINUE;
    }

    vfunc_captured_event(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;
    }

    vfunc_event(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;
    }

    vfunc_key_release_event(keyEvent) {
        let symbol = keyEvent.keyval;
        if (symbol == Clutter.KEY_Return || symbol == Clutter.KEY_space) {
            if (Main.overview.shouldToggleByCornerOrButton()) {
                Main.overview.toggle();
                return Clutter.EVENT_STOP;
            }
        }

        return Clutter.EVENT_PROPAGATE;
    }

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

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

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

const UnsafeModeIndicator = GObject.registerClass(
class UnsafeModeIndicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'channel-insecure-symbolic';

        global.context.bind_property('unsafe-mode',
            this._indicator, 'visible',
            GObject.BindingFlags.SYNC_CREATE);
    }
});

var AggregateLayout = GObject.registerClass(
class AggregateLayout extends Clutter.BoxLayout {
    _init(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.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._powerProfiles = new imports.ui.status.powerProfiles.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._location = new imports.ui.status.location.Indicator();
        this._nightLight = new imports.ui.status.nightLight.Indicator();
        this._thunderbolt = new imports.ui.status.thunderbolt.Indicator();
        this._unsafeMode = new UnsafeModeIndicator();

        this._indicators.add_child(this._remoteAccess);
        this._indicators.add_child(this._thunderbolt);
        this._indicators.add_child(this._location);
        this._indicators.add_child(this._nightLight);
        if (this._network)
            this._indicators.add_child(this._network);
        if (this._bluetooth)
            this._indicators.add_child(this._bluetooth);
        this._indicators.add_child(this._rfkill);
        this._indicators.add_child(this._volume);
        this._indicators.add_child(this._unsafeMode);
        this._indicators.add_child(this._power);
        this._indicators.add_child(this._powerProfiles);

        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._powerProfiles.menu);
        this.menu.addMenuItem(this._nightLight.menu);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        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._powerProfiles.menu.actor);
        menuLayout.addSizeChild(this._system.menu.actor);
    }
});

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,
    'dwellClick': imports.ui.status.dwellClick.DwellClickIndicator,
    'screenRecording': imports.ui.status.remoteAccess.ScreenRecordingIndicator,
};

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

        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.connect('button-press-event', this._onButtonPress.bind(this));
        this.connect('touch-event', this._onTouchEvent.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) {
        this.set_allocation(box);

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

        let [, leftNaturalWidth] = this._leftBox.get_preferred_width(-1);
        let [, centerNaturalWidth] = this._centerBox.get_preferred_width(-1);
        let [, 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);

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

        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);
    }

    _tryDragWindow(event) {
        if (Main.modalCount > 0)
            return Clutter.EVENT_PROPAGATE;

        const targetActor = global.stage.get_event_actor(event);
        if (targetActor !== this)
            return Clutter.EVENT_PROPAGATE;

        const [x, y] = event.get_coords();
        let dragWindow = this._getDraggableWindowForPosition(x);

        if (!dragWindow)
            return Clutter.EVENT_PROPAGATE;

        const button = event.type() === Clutter.EventType.BUTTON_PRESS
            ? event.get_button() : -1;

        return global.display.begin_grab_op(
            dragWindow,
            Meta.GrabOp.MOVING,
            false, /* pointer grab */
            true, /* frame action */
            button,
            event.get_state(),
            event.get_time(),
            x, y) ? Clutter.EVENT_STOP : Clutter.EVENT_PROPAGATE;
    }

    _onButtonPress(actor, event) {
        if (event.get_button() !== Clutter.BUTTON_PRIMARY)
            return Clutter.EVENT_PROPAGATE;

        return this._tryDragWindow(event);
    }

    _onTouchEvent(actor, event) {
        if (event.type() !== Clutter.EventType.TOUCH_BEGIN)
            return Clutter.EVENT_PROPAGATE;

        return this._tryDragWindow(event);
    }

    vfunc_key_press_event(keyEvent) {
        let symbol = keyEvent.keyval;
        if (symbol == Clutter.KEY_Escape) {
            global.display.focus_default_window(keyEvent.time);
            return Clutter.EVENT_STOP;
        }

        return super.vfunc_key_press_event(keyEvent);
    }

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

        let menu = indicator.menu;
        if (!indicator.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.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.includes('dateMenu'))
            Main.messageTray.bannerAlignment = Clutter.ActorAlign.START;
        else if (panel.right.includes('dateMenu'))
            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.remove_style_class_name(this._sessionStyle);

        this._sessionStyle = Main.sessionMode.panelStyle;
        if (this._sessionStyle)
            this.add_style_class_name(this._sessionStyle);
    }

    _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);
        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 ??= 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;
    }

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

        this.menuManager.addMenu(indicator.menu);

        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;
            });
    }

    _getDraggableWindowForPosition(stageX) {
        let workspaceManager = global.workspace_manager;
        const windows = workspaceManager.get_active_workspace().list_windows();
        const allWindowsByStacking =
            global.display.sort_windows_by_stacking(windows).reverse();

        return allWindowsByStacking.find(metaWindow => {
            let rect = metaWindow.get_frame_rect();
            return metaWindow.is_on_primary_monitor() &&
                   metaWindow.showing_on_its_workspace() &&
                   metaWindow.get_window_type() != Meta.WindowType.DESKTOP &&
                   metaWindow.maximized_vertically &&
                   stageX > rect.x && stageX < rect.x + rect.width;
        });
    }
});
(uuay)shellDBus.js�A// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported GnomeShell, ScreenSaverDBus */

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 { DBusSenderChecker } = imports.misc.util;
const { ControlsState } = imports.ui.overviewControls;

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._senderChecker = new DBusSenderChecker([
            'org.gnome.ControlCenter',
            'org.gnome.Settings',
            'org.gnome.SettingsDaemon.MediaKeys',
        ]);

        this._extensionsService = new GnomeShellExtensions();
        this._screenshotService = new Screenshot.ScreenshotService();

        this._grabbedAccelerators = new Map();
        this._grabbers = new Map();

        global.display.connect('accelerator-activated',
            (display, action, device, timestamp) => {
                this._emitAcceleratorActivated(action, device, timestamp);
            });

        this._cachedOverviewVisible = false;
        Main.overview.connect('showing',
                              this._checkOverviewVisibleChanged.bind(this));
        Main.overview.connect('hidden',
                              this._checkOverviewVisibleChanged.bind(this));
    }

    /**
     * Eval:
     * @param {string} code: A string containing JavaScript code
     * @returns {Array}
     *
     * 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.context.unsafe_mode)
            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];
    }

    /**
     * Focus the overview's search entry
     *
     * @async
     * @param {...any} params - method parameters
     * @param {Gio.DBusMethodInvocation} invocation - the invocation
     * @returns {void}
     */
    async FocusSearchAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        Main.overview.focusSearch();
        invocation.return_value(null);
    }

    /**
     * Show OSD with the specified parameters
     *
     * @async
     * @param {...any} params - method parameters
     * @param {Gio.DBusMethodInvocation} invocation - the invocation
     * @returns {void}
     */
    async ShowOSDAsync([params], invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        for (let param in params)
            params[param] = params[param].deep_unpack();

        const {
            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);
        invocation.return_value(null);
    }

    /**
     * Focus specified app in the overview's app grid
     *
     * @async
     * @param {string} id - an application ID
     * @param {Gio.DBusMethodInvocation} invocation - the invocation
     * @returns {void}
     */
    async FocusAppAsync([id], invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        const appSys = Shell.AppSystem.get_default();
        if (appSys.lookup_app(id) === null) {
            invocation.return_error_literal(
                Gio.DBusError,
                Gio.DBusError.FILE_NOT_FOUND,
                `No application with ID ${id}`);
            return;
        }

        Main.overview.selectApp(id);
        invocation.return_value(null);
    }

    /**
     * Show the overview's app grid
     *
     * @async
     * @param {...any} params - method parameters
     * @param {Gio.DBusMethodInvocation} invocation - the invocation
     * @returns {void}
     */
    async ShowApplicationsAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        Main.overview.show(ControlsState.APP_GRID);
        invocation.return_value(null);
    }

    async GrabAcceleratorAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        let [accel, modeFlags, grabFlags] = params;
        let sender = invocation.get_sender();
        let bindingAction = this._grabAcceleratorForSender(accel, modeFlags, grabFlags, sender);
        invocation.return_value(GLib.Variant.new('(u)', [bindingAction]));
    }

    async GrabAcceleratorsAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        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));
        }
        invocation.return_value(GLib.Variant.new('(au)', [bindingActions]));
    }

    async UngrabAcceleratorAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        let [action] = params;
        let sender = invocation.get_sender();
        let ungrabSucceeded = this._ungrabAcceleratorForSender(action, sender);

        invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
    }

    async UngrabAcceleratorsAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        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);

        invocation.return_value(GLib.Variant.new('(b)', [ungrabSucceeded]));
    }

    async ScreenTransitionAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        Main.layoutManager.screenTransition.run();

        invocation.return_value(null);
    }

    _emitAcceleratorActivated(action, device, timestamp) {
        let destination = this._grabbedAccelerators.get(action);
        if (!destination)
            return;

        let connection = this._dbusImpl.get_connection();
        let info = this._dbusImpl.get_info();
        let params = {
            'timestamp': GLib.Variant.new('u', timestamp),
            'action-mode': GLib.Variant.new('u', Main.actionMode),
        };

        let deviceNode = device.get_device_node();
        if (deviceNode)
            params['device-node'] = GLib.Variant.new('s', deviceNode);

        connection.emit_signal(
            destination,
            this._dbusImpl.get_object_path(),
            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);
    }

    async ShowMonitorLabelsAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        let sender = invocation.get_sender();
        let [dict] = params;
        Main.osdMonitorLabeler.show(sender, dict);
        invocation.return_value(null);
    }

    async HideMonitorLabelsAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        let sender = invocation.get_sender();
        Main.osdMonitorLabeler.hide(sender);
        invocation.return_value(null);
    }

    _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');

        this._userExtensionsEnabled = this.UserExtensionsEnabled;
        global.settings.connect('changed::disable-user-extensions', () => {
            if (this._userExtensionsEnabled === this.UserExtensionsEnabled)
                return;

            this._userExtensionsEnabled = this.UserExtensionsEnabled;
            this._dbusImpl.emit_property_changed('UserExtensionsEnabled',
                new GLib.Variant('b', this._userExtensionsEnabled));
        });

        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) {
        this.OpenExtensionPrefs(uuid, '', {});
    }

    OpenExtensionPrefs(uuid, parentWindow, options) {
        Main.extensionManager.openExtensionPrefs(uuid, parentWindow, options);
    }

    ReloadExtensionAsync(params, invocation) {
        invocation.return_error_literal(
            Gio.DBusError,
            Gio.DBusError.NOT_SUPPORTED,
            'ReloadExtension is deprecated and does not work');
    }

    CheckForUpdates() {
        ExtensionDownloader.checkForUpdates();
    }

    get ShellVersion() {
        return Config.PACKAGE_VERSION;
    }

    get UserExtensionsEnabled() {
        return !global.settings.get_boolean('disable-user-extensions');
    }

    set UserExtensionsEnabled(enable) {
        global.settings.set_boolean('disable-user-extensions', !enable);
    }

    _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', () => {
            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.Shell.ScreenShield',
            Gio.BusNameOwnerFlags.NONE, 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)xdndHandler.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Clutter } = 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();

        var dnd = global.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));
    }

    // Called when the user cancels the drag (i.e release the button)
    _onLeave() {
        global.window_group.disconnectObject(this);
        if (this._cursorWindowClone) {
            this._cursorWindowClone.destroy();
            this._cursorWindowClone = null;
        }

        this.emit('drag-end');
    }

    _onEnter() {
        global.window_group.connectObject('notify::visible',
            this._onWindowGroupVisibilityChanged.bind(this), 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;

            const constraintPosition = 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(constraintPosition);
        } else {
            if (!this._cursorWindowClone)
                return;

            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)
            Main.uiGroup.set_child_above_sibling(this._cursorWindowClone, null);

        let dragEvent = {
            x,
            y,
            dragActor: 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)extensionDownloader.js%// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init, installExtension, uninstallExtension, checkForUpdates */

const { Clutter, Gio, GLib, GObject, Soup } = imports.gi;

const Config = imports.misc.config;
const Desktop = imports.misc.desktop;
const Dialog = imports.ui.dialog;
const ExtensionUtils = imports.misc.extensionUtils;
const FileUtils = imports.misc.fileUtils;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;

Gio._promisify(Soup.Session.prototype, 'send_and_read_async');
Gio._promisify(Gio.OutputStream.prototype, 'write_bytes_async');
Gio._promisify(Gio.IOStream.prototype, 'close_async');
Gio._promisify(Gio.Subprocess.prototype, 'wait_check_async');

var REPOSITORY_URL_DOWNLOAD = 'https://extensions.gnome.org/download-extension/%s.shell-extension.zip';
var REPOSITORY_URL_INFO     = 'https://extensions.gnome.org/extension-info/';
var REPOSITORY_URL_UPDATE   = 'https://extensions.gnome.org/update-info/';

let _httpSession;

/**
 * @param {string} uuid - extension uuid
 * @param {Gio.DBusMethodInvocation} invocation - the caller
 * @returns {void}
 */
async function installExtension(uuid, invocation) {
    const params = {
        uuid,
        shell_version: Config.PACKAGE_VERSION,
    };

    if (Desktop.is('ubuntu') && Main.extensionManager.isModeExtension(uuid)) {
        const msg = _("This is an extension enabled by your current mode, you can't install manually any update in that session.");
        Main.notifyError(_("Can't install “%s”:").format(uuid), msg);
        invocation.return_dbus_error('org.gnome.Shell.ExtensionError', msg);
        return;
    }

    const message = Soup.Message.new_from_encoded_form('GET',
        REPOSITORY_URL_INFO,
        Soup.form_encode_hash(params));

    let info;
    try {
        const bytes = await _httpSession.send_and_read_async(
            message,
            GLib.PRIORITY_DEFAULT,
            null);
        checkResponse(message);
        const decoder = new TextDecoder();
        info = JSON.parse(decoder.decode(bytes.get_data()));
    } catch (e) {
        Main.extensionManager.logExtensionError(uuid, e);
        invocation.return_dbus_error(
            'org.gnome.Shell.ExtensionError', e.message);
        return;
    }

    const 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;
}

/**
 * Check return status of reponse
 *
 * @param {Soup.Message} message - an http response
 * @returns {void}
 * @throws
 */
function checkResponse(message) {
    const { statusCode } = message;
    const phrase = Soup.Status.get_phrase(statusCode);
    if (statusCode !== Soup.Status.OK)
        throw new Error(`Unexpected response: ${phrase}`);
}

/**
 * @param {GLib.Bytes} bytes - archive data
 * @param {Gio.File} dir - target directory
 * @returns {void}
 */
async function extractExtensionArchive(bytes, dir) {
    if (!dir.query_exists(null))
        dir.make_directory_with_parents(null);

    const [file, stream] = Gio.File.new_tmp('XXXXXX.shell-extension.zip');
    await stream.output_stream.write_bytes_async(bytes,
        GLib.PRIORITY_DEFAULT, null);
    stream.close_async(GLib.PRIORITY_DEFAULT, null);

    const unzip = Gio.Subprocess.new(
        ['unzip', '-uod', dir.get_path(), '--', file.get_path()],
        Gio.SubprocessFlags.NONE);
    await unzip.wait_check_async(null);
}

/**
 * @param {string} uuid - extension uuid
 * @returns {void}
 */
async function downloadExtensionUpdate(uuid) {
    if (!Main.extensionManager.updatesSupported)
        return;

    const dir = Gio.File.new_for_path(
        GLib.build_filenamev([global.userdatadir, 'extension-updates', uuid]));

    const params = { shell_version: Config.PACKAGE_VERSION };
    const message = Soup.Message.new_from_encoded_form('GET',
        REPOSITORY_URL_DOWNLOAD.format(uuid),
        Soup.form_encode_hash(params));

    try {
        const bytes = await _httpSession.send_and_read_async(
            message,
            GLib.PRIORITY_DEFAULT,
            null);
        checkResponse(message);

        await extractExtensionArchive(bytes, dir);
        Main.extensionManager.notifyExtensionUpdate(uuid);
    } catch (e) {
        log(`Error while downloading update for extension ${uuid}: (${e.message})`);
    }
}

/**
 * Check extensions.gnome.org for updates
 *
 * @returns {void}
 */
async function checkForUpdates() {
    if (!Main.extensionManager.updatesSupported)
        return;

    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;
        // don't updates out of repository mode extension
        if (Desktop.is("ubuntu") && Main.extensionManager.isModeExtension(uuid))
            return;
        metadatas[uuid] = {
            version: extension.metadata.version,
        };
    });

    if (Object.keys(metadatas).length === 0)
        return; // nothing to update

    const versionCheck = global.settings.get_boolean(
        'disable-extension-version-validation');
    const params = {
        shell_version: Config.PACKAGE_VERSION,
        disable_version_validation: `${versionCheck}`,
    };
    const requestBody = new GLib.Bytes(JSON.stringify(metadatas));

    const message = Soup.Message.new('POST',
        `${REPOSITORY_URL_UPDATE}?${Soup.form_encode_hash(params)}`);
    message.set_request_body_from_bytes('application/json', requestBody);

    let json;
    try {
        const bytes = await _httpSession.send_and_read_async(
            message,
            GLib.PRIORITY_DEFAULT,
            null);
        checkResponse(message);
        json = new TextDecoder().decode(bytes.get_data());
    } catch (e) {
        log(`Update check failed: ${e.message}`);
        return;
    }

    const operations = JSON.parse(json);
    const updates = [];
    for (const uuid in operations) {
        const operation = operations[uuid];
        if (operation === 'upgrade' || operation === 'downgrade')
            updates.push(uuid);
    }

    try {
        await Promise.allSettled(
            updates.map(uuid => downloadExtensionUpdate(uuid)));
    } catch (e) {
        log(`Some extension updates failed to download: ${e.message}`);
    }
}

var InstallExtensionDialog = GObject.registerClass(
class InstallExtensionDialog extends ModalDialog.ModalDialog {
    _init(uuid, info, invocation) {
        super._init({ styleClass: 'extension-dialog' });

        this._uuid = uuid;
        this._info = info;
        this._invocation = invocation;

        this.setButtons([{
            label: _('Cancel'),
            action: this._onCancelButtonPressed.bind(this),
            key: Clutter.KEY_Escape,
        }, {
            label: _('Install'),
            action: this._onInstallButtonPressed.bind(this),
            default: true,
        }]);

        let content = new Dialog.MessageDialogContent({
            title: _('Install Extension'),
            description: _('Download and install “%s” from extensions.gnome.org?').format(info.name),
        });

        this.contentLayout.add(content);
    }

    _onCancelButtonPressed() {
        this.close();
        this._invocation.return_value(GLib.Variant.new('(s)', ['cancelled']));
    }

    async _onInstallButtonPressed() {
        this.close();

        const params = { shell_version: Config.PACKAGE_VERSION };
        const message = Soup.Message.new_from_encoded_form('GET',
            REPOSITORY_URL_DOWNLOAD.format(this._uuid),
            Soup.form_encode_hash(params));

        const dir = Gio.File.new_for_path(
            GLib.build_filenamev([global.userdatadir, 'extensions', this._uuid]));

        try {
            const bytes = await _httpSession.send_and_read_async(
                message,
                GLib.PRIORITY_DEFAULT,
                null);
            checkResponse(message);

            await extractExtensionArchive(bytes, dir);

            const extension = Main.extensionManager.createExtensionObject(
                this._uuid, dir, ExtensionUtils.ExtensionType.PER_USER);
            Main.extensionManager.loadExtension(extension);
            if (!Main.extensionManager.enableExtension(this._uuid))
                throw new Error(`Cannot enable ${this._uuid}`);

            this._invocation.return_value(new GLib.Variant('(s)', ['successful']));
        } catch (e) {
            log(`Error while installing ${this._uuid}: ${e.message}`);
            this._invocation.return_dbus_error(
                'org.gnome.Shell.ExtensionError', e.message);
        }
    }
});

function init() {
    _httpSession = new Soup.Session();
}
(uuay)smartcardManager.js�
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getSmartcardManager */

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;
    }
};
Signals.addSignalMethods(SmartcardManager.prototype);
(uuay)workspaceSwitcherPopup.jsY// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WorkspaceSwitcherPopup */

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

const Layout = imports.ui.layout;
const Main = imports.ui.main;

var ANIMATION_TIME = 100;
var DISPLAY_TIMEOUT = 600;


var WorkspaceSwitcherPopup = GObject.registerClass(
class WorkspaceSwitcherPopup extends Clutter.Actor {
    _init() {
        super._init({
            offscreen_redirect: Clutter.OffscreenRedirect.ALWAYS,
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.END,
        });

        const constraint = new Layout.MonitorConstraint({ primary: true });
        this.add_constraint(constraint);

        Main.uiGroup.add_actor(this);

        this._timeoutId = 0;

        this._list = new St.BoxLayout({
            style_class: 'workspace-switcher',
        });
        this.add_child(this._list);

        this._redisplay();

        this.hide();

        let workspaceManager = global.workspace_manager;
        workspaceManager.connectObject(
            'workspace-added', this._redisplay.bind(this),
            'workspace-removed', this._redisplay.bind(this), 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++) {
            const indicator = new St.Bin({
                style_class: 'ws-switcher-indicator',
            });

            if (i === this._activeWorkspaceIndex)
                indicator.add_style_pseudo_class('active');

            this._list.add_actor(indicator);
        }
    }

    display(activeWorkspaceIndex) {
        this._activeWorkspaceIndex = activeWorkspaceIndex;

        this._redisplay();
        if (this._timeoutId != 0)
            GLib.source_remove(this._timeoutId);
        this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, DISPLAY_TIMEOUT, this._onTimeout.bind(this));
        GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._onTimeout');

        const duration = this.visible ? 0 : ANIMATION_TIME;
        this.show();
        this.opacity = 0;
        this.ease({
            opacity: 255,
            duration,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    _onTimeout() {
        GLib.source_remove(this._timeoutId);
        this._timeoutId = 0;
        this.ease({
            opacity: 0.0,
            duration: ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this.destroy(),
        });
        return GLib.SOURCE_REMOVE;
    }

    _onDestroy() {
        if (this._timeoutId)
            GLib.source_remove(this._timeoutId);
        this._timeoutId = 0;
    }
});
(uuay)ui/6[-�4�o�ZpFzg?R!Pu�	Oj\wS07ae�KmE13tl2']v,�=�`@�;5^~nA.9HX��BdW#:� perf/f�_authPrompt.js[// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported AuthPrompt */

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

const Animation = imports.ui.animation;
const AuthList = imports.gdm.authList;
const Batch = imports.gdm.batch;
const GdmUtil = imports.gdm.util;
const OVirt = imports.gdm.oVirt;
const Vmware = imports.gdm.vmware;
const Params = imports.misc.params;
const ShellEntry = imports.ui.shellEntry;
const UserWidget = imports.ui.userWidget;
const Util = imports.misc.util;

var DEFAULT_BUTTON_WELL_ICON_SIZE = 16;
var DEFAULT_BUTTON_WELL_ANIMATION_DELAY = 1000;
var DEFAULT_BUTTON_WELL_ANIMATION_TIME = 300;

var MESSAGE_FADE_OUT_ANIMATION_TIME = 500;

var AuthPromptMode = {
    UNLOCK_ONLY: 0,
    UNLOCK_OR_LOG_IN: 1,
};

var AuthPromptStatus = {
    NOT_VERIFYING: 0,
    VERIFYING: 1,
    VERIFICATION_FAILED: 2,
    VERIFICATION_SUCCEEDED: 3,
    VERIFICATION_CANCELLED: 4,
    VERIFICATION_IN_PROGRESS: 5,
};

var BeginRequestType = {
    PROVIDE_USERNAME: 0,
    DONT_PROVIDE_USERNAME: 1,
    REUSE_USERNAME: 2,
};

var AuthPrompt = GObject.registerClass({
    Signals: {
        'cancelled': {},
        'failed': {},
        'next': {},
        'prompted': {},
        'reset': { param_types: [GObject.TYPE_UINT] },
    },
}, class AuthPrompt extends St.BoxLayout {
    _init(gdmClient, mode) {
        super._init({
            style_class: 'login-dialog-prompt-layout',
            vertical: true,
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            reactive: true,
        });

        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;

        this._gdmClient = gdmClient;
        this._mode = mode;
        this._defaultButtonWellActor = null;
        this._cancelledRetries = 0;

        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 });

        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('credential-manager-authenticated', this._onCredentialManagerAuthenticated.bind(this));
        this.smartcardDetected = this._userVerifier.smartcardDetected;

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

        this._userWell = new St.Bin({
            x_expand: true,
            y_expand: true,
        });
        this.add_child(this._userWell);

        this._hasCancelButton = this._mode === AuthPromptMode.UNLOCK_OR_LOG_IN;

        this._initInputRow();

        let capsLockPlaceholder = new St.Label();
        this.add_child(capsLockPlaceholder);

        this._capsLockWarningLabel = new ShellEntry.CapsLockWarning({
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this._capsLockWarningLabel);

        this._capsLockWarningLabel.bind_property('visible',
            capsLockPlaceholder, 'visible',
            GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.INVERT_BOOLEAN);

        this._message = new St.Label({
            opacity: 0,
            styleClass: 'login-dialog-message',
            y_expand: true,
            x_expand: true,
            y_align: Clutter.ActorAlign.START,
            x_align: Clutter.ActorAlign.CENTER,
        });
        this._message.clutter_text.line_wrap = true;
        this._message.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this.add_child(this._message);
    }

    _onDestroy() {
        this._inactiveEntry.destroy();
        this._inactiveEntry = null;
        this._userVerifier.destroy();
        this._userVerifier = null;
    }

    vfunc_key_press_event(keyPressEvent) {
        if (keyPressEvent.keyval == Clutter.KEY_Escape)
            this.cancel();
        return super.vfunc_key_press_event(keyPressEvent);
    }

    _initInputRow() {
        this._mainBox = new St.BoxLayout({
            style_class: 'login-dialog-button-box',
            vertical: false,
        });
        this.add_child(this._mainBox);

        this.cancelButton = new St.Button({
            style_class: 'modal-dialog-button button cancel-button',
            accessible_name: _('Cancel'),
            button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
            reactive: this._hasCancelButton,
            can_focus: this._hasCancelButton,
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.CENTER,
            child: new St.Icon({ icon_name: 'go-previous-symbolic' }),
        });
        if (this._hasCancelButton)
            this.cancelButton.connect('clicked', () => this.cancel());
        else
            this.cancelButton.opacity = 0;
        this._mainBox.add_child(this.cancelButton);

        this._authList = new AuthList.AuthList();
        this._authList.set({
            visible: false,
        });
        this._authList.connect('activate', (list, key) => {
            this._authList.reactive = false;
            this._authList.ease({
                opacity: 0,
                duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._authList.clear();
                    this._authList.hide();
                    this._userVerifier.selectChoice(this._queryingService, key);
                },
            });
        });
        this._mainBox.add_child(this._authList);

        let entryParams = {
            style_class: 'login-dialog-prompt-entry',
            can_focus: true,
            x_expand: true,
        };

        this._entry = null;

        this._textEntry = new St.Entry(entryParams);
        ShellEntry.addContextMenu(this._textEntry, { actionMode: Shell.ActionMode.NONE });

        this._passwordEntry = new St.PasswordEntry(entryParams);
        ShellEntry.addContextMenu(this._passwordEntry, { actionMode: Shell.ActionMode.NONE });

        this._entry = this._passwordEntry;
        this._mainBox.add_child(this._entry);
        this._entry.grab_key_focus();
        this._inactiveEntry = this._textEntry;

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

        this.add_child(this._timedLoginIndicator);

        [this._textEntry, this._passwordEntry].forEach(entry => {
            entry.clutter_text.connect('text-changed', () => {
                if (!this._userVerifier.hasPendingMessages)
                    this._fadeOutMessage();
            });

            entry.clutter_text.connect('activate', () => {
                let shouldSpin = entry === this._passwordEntry;
                if (entry.reactive)
                    this._activateNext(shouldSpin);
            });
        });

        this._defaultButtonWell = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_align: Clutter.ActorAlign.END,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._defaultButtonWell.add_constraint(new Clutter.BindConstraint({
            source: this.cancelButton,
            coordinate: Clutter.BindCoordinate.WIDTH,
        }));
        this._mainBox.add_child(this._defaultButtonWell);

        this._spinner = new Animation.Spinner(DEFAULT_BUTTON_WELL_ICON_SIZE);
        this._defaultButtonWell.add_child(this._spinner);
    }

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

        this.hideTimedLoginIndicator();

        const startTime = GLib.get_monotonic_time();

        this._timedLoginTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 33,
            () => {
                const currentTime = GLib.get_monotonic_time();
                const 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.;
    }

    _activateNext(shouldSpin) {
        this.verificationStatus = AuthPromptStatus.VERIFICATION_IN_PROGRESS;
        this.updateSensitivity(false);

        if (shouldSpin)
            this.startSpinning();

        if (this._queryingService)
            this._userVerifier.answerQuery(this._queryingService, this._entry.text);
        else
            this._preemptiveAnswer = this._entry.text;

        this.emit('next');
    }

    _updateEntry(secret) {
        if (secret && this._entry !== this._passwordEntry) {
            this._mainBox.replace_child(this._entry, this._passwordEntry);
            this._entry = this._passwordEntry;
            this._inactiveEntry = this._textEntry;
        } else if (!secret && this._entry !== this._textEntry) {
            this._mainBox.replace_child(this._entry, this._textEntry);
            this._entry = this._textEntry;
            this._inactiveEntry = this._passwordEntry;
        }
        this._capsLockWarningLabel.visible = secret;
    }

    _onAskQuestion(verifier, serviceName, question, secret) {
        if (this._queryingService)
            this.clear();

        this._queryingService = serviceName;
        if (this._preemptiveAnswer) {
            this._userVerifier.answerQuery(this._queryingService, this._preemptiveAnswer);
            this._preemptiveAnswer = null;
            return;
        }

        this._updateEntry(secret);

        // Hack: The question string comes directly from PAM, if it's "Password:"
        // we replace it with our own to allow localization, if it's something
        // else we remove the last colon and any trailing or leading spaces.
        if (question === 'Password:' || question === 'Password: ')
            this.setQuestion(_('Password'));
        else
            this.setQuestion(question.replace(/: *$/, '').trim());

        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.setChoiceList(promptMessage, choiceList);
        this.updateSensitivity(true);
        this.emit('prompted');
    }

    _onCredentialManagerAuthenticated() {
        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, serviceName, message, type) {
        let wiggleParameters = { duration: 0 };

        if (type === GdmUtil.MessageType.ERROR &&
            this._userVerifier.serviceIsFingerprint(serviceName)) {
            // TODO: Use Await for wiggle to be over before unfreezing the user verifier queue
            wiggleParameters = {
                duration: 65,
                wiggleCount: 3,
            };
            this._userVerifier.increaseCurrentMessageTimeout(
                wiggleParameters.duration * (wiggleParameters.wiggleCount + 2));
        }

        this.setMessage(message, type, wiggleParameters);
        this.emit('prompted');
    }

    _onVerificationFailed(userVerifier, serviceName, canRetry) {
        const wasQueryingService = this._queryingService === serviceName;

        if (wasQueryingService) {
            this._queryingService = null;
            this.clear();
        }

        this.updateSensitivity(canRetry);
        this.setActorInDefaultButtonWell(null);

        if (!canRetry)
            this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;

        if (wasQueryingService)
            Util.wiggle(this._entry);
    }

    _onVerificationComplete() {
        this.setActorInDefaultButtonWell(null);
        this.verificationStatus = AuthPromptStatus.VERIFICATION_SUCCEEDED;
        this.cancelButton.reactive = false;
        this.cancelButton.can_focus = false;
    }

    _onReset() {
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
        this.reset();
    }

    setActorInDefaultButtonWell(actor, animate) {
        if (!this._defaultButtonWellActor &&
            !actor)
            return;

        let oldActor = this._defaultButtonWellActor;

        if (oldActor)
            oldActor.remove_all_transitions();

        let wasSpinner;
        if (oldActor == this._spinner)
            wasSpinner = true;
        else
            wasSpinner = false;

        let isSpinner;
        if (actor == this._spinner)
            isSpinner = true;
        else
            isSpinner = false;

        if (this._defaultButtonWellActor != actor && oldActor) {
            if (!animate) {
                oldActor.opacity = 0;

                if (wasSpinner) {
                    if (this._spinner)
                        this._spinner.stop();
                }
            } else {
                oldActor.ease({
                    opacity: 0,
                    duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                    mode: Clutter.AnimationMode.LINEAR,
                    onComplete: () => {
                        if (wasSpinner) {
                            if (this._spinner)
                                this._spinner.stop();
                        }
                    },
                });
            }
        }

        if (actor) {
            if (isSpinner)
                this._spinner.play();

            if (!animate) {
                actor.opacity = 255;
            } else {
                actor.ease({
                    opacity: 255,
                    duration: DEFAULT_BUTTON_WELL_ANIMATION_TIME,
                    delay: DEFAULT_BUTTON_WELL_ANIMATION_DELAY,
                    mode: Clutter.AnimationMode.LINEAR,
                });
            }
        }

        this._defaultButtonWellActor = actor;
    }

    startSpinning() {
        this.setActorInDefaultButtonWell(this._spinner, true);
    }

    stopSpinning() {
        this.setActorInDefaultButtonWell(null, false);
    }

    clear() {
        this._entry.text = '';
        this.stopSpinning();
        this._authList.clear();
        this._authList.hide();
    }

    setQuestion(question) {
        this._entry.hint_text = question;

        this._authList.hide();
        this._entry.show();
        this._entry.grab_key_focus();
    }

    _fadeInChoiceList() {
        this._authList.set({
            opacity: 0,
            visible: true,
            reactive: false,
        });
        this._authList.ease({
            opacity: 255,
            duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
            transition: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => (this._authList.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._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;
        this._message.remove_all_transitions();
        this._message.ease({
            opacity: 0,
            duration: MESSAGE_FADE_OUT_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    setMessage(message, type, wiggleParameters = { duration: 0 }) {
        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) {
            this._message.remove_all_transitions();
            this._message.text = message;
            this._message.opacity = 255;
        } else {
            this._message.opacity = 0;
        }

        Util.wiggle(this._message, wiggleParameters);
    }

    updateSensitivity(sensitive) {
        if (this._entry.reactive === sensitive)
            return;

        this._entry.reactive = sensitive;

        if (sensitive) {
            this._entry.grab_key_focus();
        } else {
            this.grab_key_focus();

            if (this._entry === this._passwordEntry)
                this._entry.password_visible = false;
        }
    }

    vfunc_hide() {
        this.setActorInDefaultButtonWell(null, true);
        super.vfunc_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();

        let userWidget = new UserWidget.UserWidget(user, Clutter.Orientation.VERTICAL);
        this._userWell.set_child(userWidget);

        if (!user)
            this._updateEntry(false);
    }

    reset() {
        let oldStatus = this.verificationStatus;
        this.verificationStatus = AuthPromptStatus.NOT_VERIFYING;
        this.cancelButton.reactive = this._hasCancelButton;
        this.cancelButton.can_focus = this._hasCancelButton;
        this._preemptiveAnswer = null;

        if (this._userVerifier)
            this._userVerifier.cancel();

        this._queryingService = null;
        this.clear();
        this._message.opacity = 0;
        this.setUser(null);
        this._updateEntry(true);
        this.stopSpinning();

        if (oldStatus == AuthPromptStatus.VERIFICATION_FAILED)
            this.emit('failed');
        else if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED)
            this.emit('cancelled');

        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
            if (oldStatus === AuthPromptStatus.VERIFICATION_CANCELLED)
                return;
            beginRequestType = BeginRequestType.PROVIDE_USERNAME;
        } else if (this._userVerifier.serviceIsForeground(OVirt.SERVICE_NAME) ||
                   this._userVerifier.serviceIsForeground(Vmware.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 if (oldStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) {
            // We're going back to retry with current user
            beginRequestType = BeginRequestType.REUSE_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;

        if (this.verificationStatus === AuthPromptStatus.VERIFICATION_IN_PROGRESS) {
            this._cancelledRetries++;
            if (this._cancelledRetries > this._userVerifier.allowedFailures)
                this.verificationStatus = AuthPromptStatus.VERIFICATION_FAILED;
        } else {
            this.verificationStatus = AuthPromptStatus.VERIFICATION_CANCELLED;
        }

        this.reset();
    }
});
(uuay)pageIndicators.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PageIndicators */

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

const INDICATOR_INACTIVE_OPACITY = 128;
const INDICATOR_INACTIVE_OPACITY_HOVER = 255;
const INDICATOR_INACTIVE_SCALE = 2 / 3;
const INDICATOR_INACTIVE_SCALE_PRESSED = 0.5;

var PageIndicators = GObject.registerClass({
    Signals: { 'page-activated': { param_types: [GObject.TYPE_INT] } },
}, class PageIndicators extends St.BoxLayout {
    _init(orientation = Clutter.Orientation.VERTICAL) {
        let vertical = orientation == Clutter.Orientation.VERTICAL;
        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._currentPosition = 0;
        this._reactive = true;
        this._reactive = true;
        this._orientation = orientation;
    }

    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;
                const indicator = new St.Button({
                    style_class: 'page-indicator',
                    button_mask: St.ButtonMask.ONE |
                                 St.ButtonMask.TWO |
                                 St.ButtonMask.THREE,
                    reactive: this._reactive,
                });
                indicator.child = new St.Widget({
                    style_class: 'page-indicator-icon',
                    pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
                });
                indicator.connect('clicked', () => {
                    this.emit('page-activated', pageIndex);
                });
                indicator.connect('notify::hover', () => {
                    this._updateIndicator(indicator, pageIndex);
                });
                indicator.connect('notify::pressed', () => {
                    this._updateIndicator(indicator, pageIndex);
                });
                this._updateIndicator(indicator, 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;
    }

    _updateIndicator(indicator, pageIndex) {
        let progress =
            Math.max(1 - Math.abs(this._currentPosition - pageIndex), 0);

        let inactiveScale = indicator.pressed
            ? INDICATOR_INACTIVE_SCALE_PRESSED : INDICATOR_INACTIVE_SCALE;
        let inactiveOpacity = indicator.hover
            ? INDICATOR_INACTIVE_OPACITY_HOVER : INDICATOR_INACTIVE_OPACITY;

        let scale = inactiveScale + (1 - inactiveScale) * progress;
        let opacity = inactiveOpacity + (255 - inactiveOpacity) * progress;

        indicator.child.set_scale(scale, scale);
        indicator.child.opacity = opacity;
    }

    setCurrentPosition(currentPosition) {
        this._currentPosition = currentPosition;

        let children = this.get_children();
        for (let i = 0; i < children.length; i++)
            this._updateIndicator(children[i], i);
    }

    get nPages() {
        return this._nPages;
    }
});
(uuay)keyboardManager.js1// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getKeyboardManager, holdKeyboard, releaseKeyboard */

const { GLib, GnomeDesktop } = 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 };
        global.backend.set_keymap(layouts, variants, options);
    }

    _applyLayoutGroupIndex(idx) {
        global.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.includes('_'))
            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;
    }

    get currentLayout() {
        return this._current;
    }
};
(uuay)vmware.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getVmwareCredentialsManager */

const Gio = imports.gi.Gio;
const Signals = imports.signals;
const Credential = imports.gdm.credentialManager;

const dbusPath = '/org/vmware/viewagent/Credentials';
const dbusInterface = 'org.vmware.viewagent.Credentials';

var SERVICE_NAME = 'gdm-vmwcred';

const VmwareCredentialsIface = `<node>
<interface name="${dbusInterface}">
<signal name="UserAuthenticated">
    <arg type="s" name="token"/>
</signal>
</interface>
</node>`;


const VmwareCredentialsInfo = Gio.DBusInterfaceInfo.new_for_xml(VmwareCredentialsIface);

let _vmwareCredentialsManager = null;

function VmwareCredentials() {
    var self = new Gio.DBusProxy({
        g_connection: Gio.DBus.session,
        g_interface_name: VmwareCredentialsInfo.name,
        g_interface_info: VmwareCredentialsInfo,
        g_name: dbusInterface,
        g_object_path: dbusPath,
        g_flags: Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES,
    });
    self.init(null);
    return self;
}

var VmwareCredentialsManager = class VmwareCredentialsManager extends Credential.CredentialManager {
    constructor() {
        super(SERVICE_NAME);
        this._credentials = new VmwareCredentials();
        this._credentials.connectSignal('UserAuthenticated',
            (proxy, sender, [token]) => {
                this.token = token;
            });
    }
};
Signals.addSignalMethods(VmwareCredentialsManager.prototype);

function getVmwareCredentialsManager() {
    if (!_vmwareCredentialsManager)
        _vmwareCredentialsManager = new VmwareCredentialsManager();

    return _vmwareCredentialsManager;
}
(uuay)shell/+
%$gdm/i&��JqM)pointerWatcher.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getPointerWatcher */

const { GLib } = imports.gi;

// 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 = global.backend.get_core_idle_monitor();
        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() {
        this._idle = false;
        this._updatePointer();
        this._updateTimeout();
    }

    _onIdleMonitorBecameIdle() {
        this._idle = true;
        this._idleMonitor.add_user_active_watch(this._onIdleMonitorBecameActive.bind(this));
        this._updateTimeout();
    }

    _updateTimeout() {
        if (this._timeoutId) {
            GLib.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 = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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] = 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)appMenu.js�$// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported AppMenu */
const { Clutter, Gio, GLib, Meta, Shell, St } = imports.gi;

const AppFavorites = imports.ui.appFavorites;
const Main = imports.ui.main;
const ParentalControlsManager = imports.misc.parentalControlsManager;
const PopupMenu = imports.ui.popupMenu;

var AppMenu = class AppMenu extends PopupMenu.PopupMenu {
    /**
     * @param {Clutter.Actor} sourceActor - actor the menu is attached to
     * @param {St.Side} side - arrow side
     * @param {object} params - options
     * @param {bool} params.favoritesSection - show items to add/remove favorite
     * @param {bool} params.showSingleWindow - show window section for a single window
     */
    constructor(sourceActor, side = St.Side.TOP, params = {}) {
        if (Clutter.get_default_text_direction() === Clutter.TextDirection.RTL) {
            if (side === St.Side.LEFT)
                side = St.Side.RIGHT;
            else if (side === St.Side.RIGHT)
                side = St.Side.LEFT;
        }

        super(sourceActor, 0.5, side);

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

        const {
            favoritesSection = false,
            showSingleWindows = false,
        } = params;

        this._app = null;
        this._appSystem = Shell.AppSystem.get_default();
        this._parentalControlsManager = ParentalControlsManager.getDefault();
        this._appFavorites = AppFavorites.getAppFavorites();
        this._enableFavorites = favoritesSection;
        this._showSingleWindows = showSingleWindows;

        this._windowsChangedId = 0;
        this._updateWindowsLaterId = 0;

        /* Translators: This is the heading of a list of open windows */
        this._openWindowsHeader = new PopupMenu.PopupSeparatorMenuItem(_('Open Windows'));
        this.addMenuItem(this._openWindowsHeader);

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

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

        this._newWindowItem = this.addAction(_('New Window'), () => {
            this._animateLaunch();
            this._app.open_new_window(-1);
            Main.overview.hide();
        });

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

        this._onGpuMenuItem = this.addAction('', () => {
            this._animateLaunch();
            this._app.launch(0, -1, this._getNonDefaultLaunchGpu());
            Main.overview.hide();
        });

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

        this._toggleFavoriteItem = this.addAction('', () => {
            const appId = this._app.get_id();
            if (this._appFavorites.isFavorite(appId))
                this._appFavorites.removeFavorite(appId);
            else
                this._appFavorites.addFavorite(appId);
        });

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

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

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

        this._quitItem =
            this.addAction(_('Quit'), () => this._app.request_quit());

        this._appSystem.connectObject(
            'installed-changed', () => this._updateDetailsVisibility(),
            'app-state-changed', this._onAppStateChanged.bind(this),
            this.actor);

        this._parentalControlsManager.connectObject(
            'app-filter-changed', () => this._updateFavoriteItem(), this.actor);

        this._appFavorites.connectObject(
            'changed', () => this._updateFavoriteItem(), this.actor);

        global.settings.connectObject(
            'writable-changed::favorite-apps', () => this._updateFavoriteItem(),
            this.actor);

        global.connectObject(
            'notify::switcheroo-control', () => this._updateGpuItem(),
            this.actor);

        this._updateQuitItem();
        this._updateFavoriteItem();
        this._updateGpuItem();
        this._updateDetailsVisibility();
    }

    _onAppStateChanged(sys, app) {
        if (this._app !== app)
            return;

        this._updateQuitItem();
        this._updateNewWindowItem();
        this._updateGpuItem();
    }

    _updateQuitItem() {
        this._quitItem.visible = this._app?.state === Shell.AppState.RUNNING;
    }

    _updateNewWindowItem() {
        const actions = this._app?.appInfo?.list_actions() ?? [];
        this._newWindowItem.visible =
            this._app?.can_open_new_window() && !actions.includes('new-window');
    }

    _updateFavoriteItem() {
        const appInfo = this._app?.app_info;
        const canFavorite = appInfo &&
            this._enableFavorites &&
            global.settings.is_writable('favorite-apps') &&
            this._parentalControlsManager.shouldShowApp(appInfo);

        this._toggleFavoriteItem.visible = canFavorite;

        if (!canFavorite)
            return;

        const { id } = this._app;
        const isUbuntu = imports.misc.desktop.is('ubuntu');
        this._toggleFavoriteItem.label.text = this._appFavorites.isFavorite(id)
            ? (isUbuntu ? _('Remove from Favorites') : _('Unpin'))
            : (isUbuntu ? _('Add to Favorites') : _('Pin to Dash'));
    }

    _updateGpuItem() {
        const proxy = global.get_switcheroo_control();
        const hasDualGpu = proxy?.get_cached_property('HasDualGpu')?.unpack();

        const showItem =
            this._app?.state === Shell.AppState.STOPPED && hasDualGpu;

        this._onGpuMenuItem.visible = showItem;

        if (!showItem)
            return;

        const launchGpu = this._getNonDefaultLaunchGpu();
        this._onGpuMenuItem.label.text = launchGpu === Shell.AppLaunchGpu.DEFAULT
            ? _('Launch using Integrated Graphics Card')
            : _('Launch using Discrete Graphics Card');
    }

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

    _animateLaunch() {
        if (this.sourceActor.animateLaunch)
            this.sourceActor.animateLaunch();
    }

    _getNonDefaultLaunchGpu() {
        return this._app.appInfo.get_boolean('PrefersNonDefaultGPU')
            ? Shell.AppLaunchGpu.DEFAULT
            : Shell.AppLaunchGpu.DISCRETE;
    }

    /** */
    destroy() {
        super.destroy();

        this.setApp(null);
    }

    /**
     * @returns {bool} - true if the menu is empty
     */
    isEmpty() {
        if (!this._app)
            return true;
        return super.isEmpty();
    }

    /**
     * @param {Shell.App} app - the app the menu represents
     */
    setApp(app) {
        if (this._app === app)
            return;

        this._app?.disconnectObject(this);

        this._app = app;

        this._app?.connectObject('windows-changed',
            () => this._queueUpdateWindowsSection(), this);

        this._updateWindowsSection();

        const appInfo = app?.app_info;
        const actions = appInfo?.list_actions() ?? [];

        this._actionSection.removeAll();
        actions.forEach(action => {
            const label = appInfo.get_action_name(action);
            this._actionSection.addAction(label, event => {
                if (action === 'new-window')
                    this._animateLaunch();

                this._app.launch_action(action, event.get_time(), -1);
                Main.overview.hide();
            });
        });

        this._updateQuitItem();
        this._updateNewWindowItem();
        this._updateFavoriteItem();
        this._updateGpuItem();
    }

    _queueUpdateWindowsSection() {
        if (this._updateWindowsLaterId)
            return;

        this._updateWindowsLaterId = Meta.later_add(
            Meta.LaterType.BEFORE_REDRAW, () => {
                this._updateWindowsSection();
                return GLib.SOURCE_REMOVE;
            });
    }

    _updateWindowsSection() {
        if (this._updateWindowsLaterId)
            Meta.later_remove(this._updateWindowsLaterId);
        this._updateWindowsLaterId = 0;

        this._windowSection.removeAll();
        this._openWindowsHeader.hide();

        if (!this._app)
            return;

        const minWindows = this._showSingleWindows ? 1 : 2;
        const windows = this._app.get_windows().filter(w => !w.skip_taskbar);
        if (windows.length < minWindows)
            return;

        this._openWindowsHeader.show();

        windows.forEach(window => {
            const title = window.title || this._app.get_name();
            const item = this._windowSection.addAction(title, event => {
                Main.activateWindow(window, event.get_time());
            });
            window.connectObject('notify::title', () => {
                item.label.text = window.title || this._app.get_name();
            }, item);
        });
    }
};
(uuay)switchMonitor.jsy// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SwitchMonitorPopup */

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 = [];

        items.push({
            icon: 'view-mirror-symbolic',
            /* Translators: this is for display mirroring i.e. cloning.
             * Try to keep it under around 15 characters.
             */
            label: _('Mirror'),
            configType: Meta.MonitorSwitchConfigType.ALL_MIRROR,
        });

        items.push({
            icon: 'video-joined-displays-symbolic',
            /* Translators: this is for the desktop spanning displays.
             * Try to keep it under around 15 characters.
             */
            label: _('Join Displays'),
            configType: Meta.MonitorSwitchConfigType.ALL_LINEAR,
        });

        if (global.backend.get_monitor_manager().has_builtin_panel) {
            items.push({
                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'),
                configType: Meta.MonitorSwitchConfigType.EXTERNAL,
            });
            items.push({
                icon: 'computer-symbolic',
                /* Translators: this is for using only the laptop display.
                 * Try to keep it under around 15 characters.
                 */
                label: _('Built-in Only'),
                configType: Meta.MonitorSwitchConfigType.BUILTIN,
            });
        }

        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) % this._items.length;
        this._select(selectConfig);
    }

    _keyPressHandler(keysym, action) {
        if (action == Meta.KeyBindingAction.SWITCH_MONITOR)
            this._select(this._next());
        else if (keysym == Clutter.KEY_Left)
            this._select(this._previous());
        else if (keysym == Clutter.KEY_Right)
            this._select(this._next());
        else
            return Clutter.EVENT_PROPAGATE;

        return Clutter.EVENT_STOP;
    }

    _finish() {
        super._finish();

        const monitorManager = global.backend.get_monitor_manager();
        const item = this._items[this._selectedIndex];

        monitorManager.switch_config(item.configType);
    }
});

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) {
        const box = new St.BoxLayout({
            style_class: 'alt-tab-app',
            vertical: true,
        });

        const icon = new St.Icon({
            icon_name: item.icon,
            icon_size: APP_ICON_SIZE,
        });
        box.add_child(icon);

        let text = new St.Label({
            text: item.label,
            x_align: Clutter.ActorAlign.CENTER,
        });
        box.add_child(text);

        this.addItem(box, text);
    }
});
(uuay)gnomeSession.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PresenceStatus, Presence, Inhibitor, SessionManager, InhibitFlags */

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);
}

var InhibitFlags = {
    LOGOUT: 1 << 0,
    SWITCH: 1 << 1,
    SUSPEND: 1 << 2,
    IDLE: 1 << 3,
    AUTOMOUNT: 1 << 4,
};
(uuay)locatePointer.jsc// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported LocatePointer */

const { Gio } = imports.gi;
const Ripples = imports.ui.ripples;
const Main = imports.ui.main;

const LOCATE_POINTER_KEY = "locate-pointer";
const LOCATE_POINTER_SCHEMA = "org.gnome.desktop.interface";

var LocatePointer = class {
    constructor() {
        this._settings = new Gio.Settings({ schema_id: LOCATE_POINTER_SCHEMA });
        this._settings.connect(`changed::${LOCATE_POINTER_KEY}`, () => this._syncEnabled());
        this._syncEnabled();
    }

    _syncEnabled() {
        let enabled = this._settings.get_boolean(LOCATE_POINTER_KEY);
        if (enabled == !!this._ripples)
            return;

        if (enabled) {
            this._ripples = new Ripples.Ripples(0.5, 0.5, 'ripple-pointer-location');
            this._ripples.addTo(Main.uiGroup);
        } else {
            this._ripples.destroy();
            this._ripples = null;
        }
    }

    show() {
        if (!this._ripples)
            return;

        let [x, y] = global.get_pointer();
        this._ripples.playAnimation(x, y);
    }
};
(uuay)notificationDaemon.js�c// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported NotificationDaemon */

const { GdkPixbuf, Gio, GLib, GObject, Shell, St } = imports.gi;

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;

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,
};

var FdoNotificationDaemon = class FdoNotificationDaemon {
    constructor() {
        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(FdoNotificationsIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/freedesktop/Notifications');

        this._sources = [];
        this._notifications = {};

        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']) {
            const [
                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.
    _getSource(title, pid, ndata, sender) {
        if (!pid && !(ndata && ndata.notification))
            throw new Error('Either a pid or ndata.notification is needed');

        // 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;
        }

        const appId = ndata?.hints['desktop-entry'];
        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 idleId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                this._emitNotificationClosed(id, NotificationClosedReason.DISMISSED);
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(idleId, '[gnome-shell] this._emitNotificationClosed');
            return invocation.return_value(GLib.Variant.new('(u)', [id]));
        }

        // 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'];
        }

        const ndata = {
            appName,
            icon,
            summary,
            body,
            actions,
            hints,
            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 = hints['sender-pid'];

        let source = this._getSource(appName, pid, ndata, sender, null);
        this._notifyForSource(source, ndata);

        return invocation.return_value(GLib.Variant.new('(u)', [id]));
    }

    _notifyForSource(source, ndata) {
        const { icon, summary, body, actions, hints } = ndata;
        let { notification } = ndata;

        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);
            });
        }

        // 'image-data' (or 'image-path') takes precedence over 'app-icon'.
        let gicon = this._imageForNotificationData(hints);

        if (!gicon)
            gicon = this._iconForNotificationData(icon);

        if (!gicon)
            gicon = this._fallbackIconForNotificationData(hints);

        const soundFile = 'sound-file' in hints
            ? Gio.File.new_for_path(hints['sound-file']) : null;

        notification.update(summary, body, {
            gicon,
            bannerMarkup: true,
            clear: true,
            soundFile,
            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 = GObject.registerClass(
class FdoNotificationDaemonSource extends MessageTray.Source {
    _init(title, pid, sender, appId) {
        this.pid = pid;
        this.initialTitle = title;
        this.app = this._getApp(appId);

        super._init(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.showNotification(notification);
    }

    _getApp(appId) {
        const appSys = Shell.AppSystem.get_default();
        let app;

        app = Shell.WindowTracker.get_default().get_app_from_pid(this.pid);
        if (app != null)
            return app;

        if (appId)
            app = appSys.lookup_app(`${appId}.desktop`);

        if (!app)
            app = appSys.lookup_app(`${this.initialTitle}.desktop`);

        return app;
    }

    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 = GObject.registerClass(
class GtkNotificationDaemonNotification extends MessageTray.Notification {
    _init(source, notification) {
        super._init(source);
        this._serialized = GLib.Variant.new('a{sv}', notification);

        const {
            title,
            body,
            icon: gicon,
            urgent,
            priority,
            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?.unpack();
        this._defaultActionTarget = defaultActionTarget;

        this.update(title.unpack(), body?.unpack(), {
            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, target } = button;
        this._activateAction(action.unpack(), target);
    }

    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 = GObject.registerClass(
class GtkNotificationDaemonAppSource extends MessageTray.Source {
    _init(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();

        this._appId = appId;
        this._app = app;
        this._objectPath = objectPath;

        super._init(app.get_name());

        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.showNotification(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('notify::count', 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 "${appId}" could not be found`);
                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)padOsd.js=�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PadOsd, PadOsdService */

const {
    Atk, Clutter, GDesktopEnums, Gio,
    GLib, GObject, Gtk, Meta, Pango, 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 = GObject.registerClass({
    Signals: { 'pad-selected': { param_types: [Clutter.InputDevice.$gtype] } },
}, class PadChooser extends St.Button {
    _init(device, groupDevices) {
        super._init({
            style_class: 'pad-chooser-button',
            toggle_mode: true,
        });
        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,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.set_child(arrow);
        this._ensureMenu(groupDevices);

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

    vfunc_clicked() {
        if (this.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, 0.5, St.Side.TOP);
        this._padChooserMenu.connect('menu-closed', () => {
            this.set_checked(false);
        });
        this._padChooserMenu.actor.hide();
        Main.uiGroup.add_actor(this._padChooserMenu.actor);

        this._menuManager = new PopupMenu.PopupMenuManager(this);
        this._menuManager.addMenu(this._padChooserMenu);

        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.set_checked(false);
        this._ensureMenu(devices);
    }
});

var KeybindingEntry = GObject.registerClass({
    Signals: { 'keybinding-edited': { param_types: [GObject.TYPE_STRING] } },
}, class KeybindingEntry extends St.Entry {
    _init() {
        super._init({ hint_text: _("New shortcut…"), style: 'width: 10em' });
    }

    vfunc_captured_event(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.set_text(str);
        this.emit('keybinding-edited', str);
        return Clutter.EVENT_STOP;
    }
});

var ActionComboBox = GObject.registerClass({
    Signals: { 'action-selected': { param_types: [GObject.TYPE_INT] } },
}, class ActionComboBox extends St.Button {
    _init() {
        super._init({ style_class: 'button' });
        this.set_toggle_mode(true);

        const boxLayout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.HORIZONTAL,
            spacing: 6,
        });
        let box = new St.Widget({ layout_manager: boxLayout });
        this.set_child(box);

        this._label = new St.Label({ style_class: 'combo-box-label' });
        box.add_child(this._label);

        const 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, 0, St.Side.TOP);
        this._editMenu.connect('menu-closed', () => {
            this.set_checked(false);
        });
        this._editMenu.actor.hide();
        Main.uiGroup.add_actor(this._editMenu.actor);

        this._editMenuManager = new PopupMenu.PopupMenuManager(this);
        this._editMenuManager.addMenu(this._editMenu);

        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);
    }

    vfunc_clicked() {
        if (this.get_checked())
            this.popup();
        else
            this.popdown();
    }

    setButtonActionsActive(active) {
        this._buttonItems.forEach(item => item.setSensitive(active));
    }
});

var ActionEditor = GObject.registerClass({
    Signals: { 'done': {} },
}, class ActionEditor extends St.Widget {
    _init() {
        const boxLayout = new Clutter.BoxLayout({
            orientation: Clutter.Orientation.HORIZONTAL,
            spacing: 12,
        });

        super._init({ layout_manager: boxLayout });

        this._actionComboBox = new ActionComboBox();
        this._actionComboBox.connect('action-selected', this._onActionSelected.bind(this));
        this.add_actor(this._actionComboBox);

        this._keybindingEdit = new KeybindingEntry();
        this._keybindingEdit.connect('keybinding-edited', this._onKeybindingEdited.bind(this));
        this.add_actor(this._keybindingEdit);

        this._doneButton = new St.Button({
            label: _('Done'),
            style_class: 'button',
            x_expand: false,
        });
        this._doneButton.connect('clicked', this._onEditingDone.bind(this));
        this.add_actor(this._doneButton);
    }

    _updateKeybindingEntryState() {
        if (this._currentAction == GDesktopEnums.PadButtonAction.KEYBINDING) {
            this._keybindingEdit.set_text(this._currentKeybinding);
            this._keybindingEdit.show();
            this._keybindingEdit.grab_key_focus();
        } else {
            this._keybindingEdit.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.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');
    }
});

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] = file.load_contents(null);
        this._curEdited = null;
        this._prevEdited = null;
        this._css = new TextDecoder().decode(css);
        this._labels = [];
        this._activeButtons = [];
        super._init(params);
    }

    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();
        this._initLabels();
    }

    get editorActor() {
        return this._editorActor;
    }

    set editorActor(actor) {
        actor.hide();
        this._editorActor = actor;
        this.add_actor(actor);
    }

    _initLabels() {
        let i = 0;
        for (i = 0; ; i++) {
            if (!this._addLabel(Meta.PadActionType.BUTTON, i))
                break;
        }

        for (i = 0; ; i++) {
            if (!this._addLabel(Meta.PadActionType.RING, i, CW) ||
                !this._addLabel(Meta.PadActionType.RING, i, CCW))
                break;
        }

        for (i = 0; ; i++) {
            if (!this._addLabel(Meta.PadActionType.STRIP, i, UP) ||
                !this._addLabel(Meta.PadActionType.STRIP, i, DOWN))
                break;
        }
    }

    _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 '%s%s%s'.format(
            '</style>',
            '<xi:include href="%s" />'.format(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}.Leader { stroke: ${ACTIVE_COLOR} !important; }`;
            css += `.${ch}.Button { 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() {
        [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 [, natHeight] = child.get_preferred_height(-1);
        let [, natWidth] = child.get_preferred_width(natHeight);
        let childBox = new Clutter.ActorBox();

        // 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;

        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);
    }

    vfunc_allocate(box) {
        super.vfunc_allocate(box);
        if (this._handle === null)
            return;

        this._updateDiagramScale();

        for (let i = 0; i < this._labels.length; i++) {
            const { label, x, y, arrangement } = this._labels[i];
            this._allocateChild(label, x, y, arrangement);
        }

        if (this._editorActor && this._curEdited) {
            const { x, y, arrangement } = this._curEdited;
            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();
    }

    _getItemLabelCoords(labelName, leaderName) {
        if (this._handle == null)
            return [false];

        const [labelFound, labelPos] = this._handle.get_position_sub(`#${labelName}`);
        const [, labelSize] = this._handle.get_dimensions_sub(`#${labelName}`);
        if (!labelFound)
            return [false];

        const [leaderFound, leaderPos] = this._handle.get_position_sub(`#${leaderName}`);
        const [, leaderSize] = this._handle.get_dimensions_sub(`#${leaderName}`);
        if (!leaderFound)
            return [false];

        let direction;
        if (labelPos.x > leaderPos.x + leaderSize.width)
            direction = LTR;
        else
            direction = RTL;

        let pos = { x: labelPos.x, y: labelPos.y + labelSize.height };
        if (this.leftHanded) {
            direction = 1 - direction;
            pos.x = this._imageWidth - pos.x;
            pos.y = this._imageHeight - pos.y;
        }

        return [true, pos.x, pos.y, direction];
    }

    _getButtonLabels(button) {
        let ch = String.fromCharCode('A'.charCodeAt() + button);
        const labelName = `Label${ch}`;
        const leaderName = `Leader${ch}`;
        return [labelName, leaderName];
    }

    _getRingLabels(number, dir) {
        let numStr = number > 0 ? (number + 1).toString() : '';
        let dirStr = dir == CW ? 'CW' : 'CCW';
        const labelName = `LabelRing${numStr}${dirStr}`;
        const leaderName = `LeaderRing${numStr}${dirStr}`;
        return [labelName, leaderName];
    }

    _getStripLabels(number, dir) {
        let numStr = number > 0 ? (number + 1).toString() : '';
        let dirStr = dir == UP ? 'Up' : 'Down';
        const labelName = `LabelStrip${numStr}${dirStr}`;
        const leaderName = `LeaderStrip${numStr}${dirStr}`;
        return [labelName, leaderName];
    }

    _getLabelCoords(action, idx, dir) {
        if (action == Meta.PadActionType.BUTTON)
            return this._getItemLabelCoords(...this._getButtonLabels(idx));
        else if (action == Meta.PadActionType.RING)
            return this._getItemLabelCoords(...this._getRingLabels(idx, dir));
        else if (action == Meta.PadActionType.STRIP)
            return this._getItemLabelCoords(...this._getStripLabels(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(action, idx, dir) {
        let [found, x, y, arrangement] = this._getLabelCoords(action, idx, dir);
        if (!found)
            return false;

        let label = new St.Label();
        this._labels.push({ label, action, idx, dir, x, y, arrangement });
        this.add_actor(label);
        return true;
    }

    updateLabels(getText) {
        for (let i = 0; i < this._labels.length; i++) {
            const { label, action, idx, dir } = this._labels[i];
            let str = getText(action, idx, dir);
            label.set_text(str);
        }

        this.queue_relayout();
    }

    _applyLabel(label, action, idx, dir, str) {
        if (str !== null)
            label.set_text(str);
        label.show();
    }

    stopEdition(continues, str) {
        this._editorActor.hide();

        if (this._prevEdited) {
            const { label, action, idx, dir } = this._prevEdited;
            this._applyLabel(label, action, idx, dir, str);
            this._prevEdited = null;
        }

        if (this._curEdited) {
            const { label, action, idx, dir } = this._curEdited;
            this._applyLabel(label, action, idx, dir, str);
            if (continues)
                this._prevEdited = this._curEdited;
            this._curEdited = null;
        }

        this.queue_relayout();
    }

    startEdition(action, idx, dir) {
        let editedLabel;

        if (this._curEdited)
            return;

        for (let i = 0; i < this._labels.length; i++) {
            if (action == this._labels[i].action &&
                idx == this._labels[i].idx && dir == this._labels[i].dir) {
                this._curEdited = this._labels[i];
                editedLabel = this._curEdited.label;
                break;
            }
        }

        if (this._curEdited == null)
            return;
        this._editorActor.show();
        editedLabel.hide();
        this.queue_relayout();
    }
});

var PadOsd = GObject.registerClass({
    Signals: {
        'pad-selected': { param_types: [Clutter.InputDevice.$gtype] },
        'closed': {},
    },
}, class PadOsd extends St.BoxLayout {
    _init(padDevice, settings, imagePath, editionMode, monitorIndex) {
        super._init({
            style_class: 'pad-osd-window',
            vertical: true,
            x_expand: true,
            y_expand: true,
            reactive: true,
        });

        this.padDevice = padDevice;
        this._groupPads = [padDevice];
        this._settings = settings;
        this._imagePath = imagePath;
        this._editionMode = editionMode;
        this._padChooser = null;

        let seat = Clutter.get_default_backend().get_default_seat();
        seat.connectObject(
            'device-added', (_seat, device) => {
                if (device.get_device_type() === Clutter.InputDeviceType.PAD_DEVICE &&
                    this.padDevice.is_grouped(device)) {
                    this._groupPads.push(device);
                    this._updatePadChooser();
                }
            },
            'device-removed', (_seat, device) => {
                // If the device is being removed, destroy the padOsd.
                if (device === this.padDevice) {
                    this.destroy();
                } else if (this._groupPads.includes(device)) {
                    // Or update the pad chooser if the device belongs to
                    // the same group.
                    this._groupPads.splice(this._groupPads.indexOf(device), 1);
                    this._updatePadChooser();
                }
            }, this);

        seat.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.connect('destroy', this._onDestroy.bind(this));
        Main.uiGroup.add_actor(this);

        this._monitorIndex = monitorIndex;
        let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
        this.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.add_actor(this._titleBox);

        const 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_ellipsize(Pango.EllipsizeMode.NONE);
        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,
            x_expand: true,
            y_expand: true,
        });
        this.add_actor(this._padDiagram);
        this._updateActionLabels();

        const buttonBox = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_actor(buttonBox);
        this._editButton = new St.Button({
            label: _('Edit…'),
            style_class: 'button',
            can_focus: true,
            x_align: Clutter.ActorAlign.CENTER,
        });
        this._editButton.connect('clicked', () => {
            this.setEditionMode(true);
        });
        buttonBox.add_actor(this._editButton);

        this._syncEditionMode();
        this._grab = Main.pushModal(this);
    }

    _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);
            } 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.includes(pad))
            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 ?? _('None');
    }

    _updateActionLabels() {
        this._padDiagram.updateLabels(this._getActionText.bind(this));
    }

    vfunc_captured_event(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.KEY_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.includes(event.get_source_device())) {
            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.set_text(title);
    }

    _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 ?? _('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 };

        const 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);
        const key = `ring${ch}-${dir === CCW ? 'ccw' : 'cw'}-mode-${mode}`;
        this._startActionEdition(key, Meta.PadActionType.RING, ring, dir, mode);
    }

    _startStripActionEdition(strip, dir, mode) {
        let ch = String.fromCharCode('A'.charCodeAt() + strip);
        const key = `strip${ch}-${dir === UP ? 'up' : 'down'}-mode-${mode}`;
        this._startActionEdition(key, Meta.PadActionType.STRIP, strip, dir, mode);
    }

    setEditionMode(editionMode) {
        if (this._editionMode == editionMode)
            return;

        this._editionMode = editionMode;
        this._syncEditionMode();
    }

    _onDestroy() {
        Main.popModal(this._grab);
        this._grab = null;
        this._actionEditor.close();

        this.emit('closed');
    }
});

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 seat = Clutter.get_default_backend().get_default_seat();
        let devices = seat.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)osdWindow.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported OsdWindowManager */

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

const BarLevel = imports.ui.barLevel;
const Layout = imports.ui.layout;
const Main = imports.ui.main;

var HIDE_TIMEOUT = 1500;
var FADE_TIME = 100;
var LEVEL_ANIMATION_TIME = 100;

var OsdWindow = GObject.registerClass(
class OsdWindow extends Clutter.Actor {
    _init(monitorIndex) {
        super._init({
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.END,
        });

        this._monitorIndex = monitorIndex;
        let constraint = new Layout.MonitorConstraint({ index: monitorIndex });
        this.add_constraint(constraint);

        this._hbox = new St.BoxLayout({
            style_class: 'osd-window',
        });
        this.add_actor(this._hbox);

        this._icon = new St.Icon({ y_expand: true });
        this._hbox.add_child(this._icon);

        this._vbox = new St.BoxLayout({
            vertical: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._hbox.add_child(this._vbox);

        this._label = new St.Label();
        this._vbox.add_child(this._label);

        this._level = new BarLevel.BarLevel({
            style_class: 'level',
            value: 0,
        });
        this._vbox.add_child(this._level);

        this._hideTimeoutId = 0;
        this._reset();
        Main.uiGroup.add_child(this);
    }

    _updateBoxVisibility() {
        this._vbox.visible = [...this._vbox].some(c => c.visible);
    }

    setIcon(icon) {
        this._icon.gicon = icon;
    }

    setLabel(label) {
        this._label.visible = label != undefined;
        if (label)
            this._label.text = label;
        this._updateBoxVisibility();
    }

    setLevel(value) {
        this._level.visible = value != undefined;
        if (value != undefined) {
            if (this.visible) {
                this._level.ease_property('value', value, {
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    duration: LEVEL_ANIMATION_TIME,
                });
            } else {
                this._level.value = value;
            }
        }
        this._updateBoxVisibility();
    }

    setMaxLevel(maxLevel = 1) {
        this._level.maximum_value = maxLevel;
    }

    show() {
        if (!this._icon.gicon)
            return;

        if (!this.visible) {
            Meta.disable_unredirect_for_display(global.display);
            super.show();
            this.opacity = 0;
            this.get_parent().set_child_above_sibling(this, null);

            this.ease({
                opacity: 255,
                duration: FADE_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        }

        if (this._hideTimeoutId)
            GLib.source_remove(this._hideTimeoutId);
        this._hideTimeoutId = GLib.timeout_add(
            GLib.PRIORITY_DEFAULT, HIDE_TIMEOUT, this._hide.bind(this));
        GLib.Source.set_name_by_id(this._hideTimeoutId, '[gnome-shell] this._hide');
    }

    cancel() {
        if (!this._hideTimeoutId)
            return;

        GLib.source_remove(this._hideTimeoutId);
        this._hide();
    }

    _hide() {
        this._hideTimeoutId = 0;
        this.ease({
            opacity: 0,
            duration: FADE_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._reset();
                Meta.enable_unredirect_for_display(global.display);
            },
        });
        return GLib.SOURCE_REMOVE;
    }

    _reset() {
        super.hide();
        this.setLabel(null);
        this.setMaxLevel(null);
        this.setLevel(null);
    }
});

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].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)background.js�s// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SystemBackground */

// 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, GObject, GnomeDesktop, Meta } = imports.gi;
const Signals = imports.signals;

const LoginManager = imports.misc.loginManager;
const Main = imports.ui.main;
const Params = imports.misc.params;

Gio._promisify(Gio.File.prototype, 'query_info_async');

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_URI_KEY = 'picture-uri';
const PICTURE_URI_DARK_KEY = 'picture-uri-dark';

const INTERFACE_SCHEMA = 'org.gnome.desktop.interface';
const COLOR_SCHEME_KEY = 'color-scheme';

var FADE_ANIMATION_TIME = 1000;

// 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, theFile, 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_async(null, () => {
            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 = GObject.registerClass({
    Signals: { 'loaded': {}, 'bg-changed': {} },
}, class Background extends Meta.Background {
    _init(params) {
        params = Params.parse(params, {
            monitorIndex: 0,
            layoutManager: Main.layoutManager,
            settings: null,
            file: null,
            style: null,
        });

        super._init({ meta_display: global.display });

        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._interfaceSettings = new Gio.Settings({ schema_id: INTERFACE_SCHEMA });

        this._clock = new GnomeDesktop.WallClock();
        this._clock.connectObject('notify::timezone',
            () => {
                if (this._animation)
                    this._loadAnimation(this._animation.file);
            }, this);

        let loginManager = LoginManager.getLoginManager();
        loginManager.connectObject('prepare-for-sleep',
            (lm, aboutToSuspend) => {
                if (aboutToSuspend)
                    return;
                this._refreshAnimation();
            }, this);

        this._settings.connectObject('changed',
            this._emitChangedSignal.bind(this), this);

        this._interfaceSettings.connectObject(`changed::${COLOR_SCHEME_KEY}`,
            this._emitChangedSignal.bind(this), this);

        this._load();
    }

    destroy() {
        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;

        this._clock.disconnectObject(this);
        this._clock = null;

        LoginManager.getLoginManager().disconnectObject(this);
        this._settings.disconnectObject(this);
        this._interfaceSettings.disconnectObject(this);

        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('bg-changed');
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(this._changedIdleId,
            '[gnome-shell] Background._emitChangedSignal');
    }

    updateResolution() {
        if (this._animation)
            this._refreshAnimation();
    }

    _refreshAnimation() {
        if (!this._animation)
            return;

        this._removeAnimationTimeout();
        this._updateAnimation();
    }

    _setLoaded() {
        if (this.isLoaded)
            return;

        this.isLoaded = true;
        if (this._cancellable?.is_cancelled())
            return;

        let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this.emit('loaded');
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(id, '[gnome-shell] Background._setLoaded Idle');
    }

    _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.set_color(color);
        else
            this.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.set_blend(files[0], files[1],
                               this._animation.transitionProgress,
                               this._style);
            } else if (files.length > 0) {
                this.set_file(files[0], this._style);
            } else {
                this.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 {
                // eslint-disable-next-line no-loop-func
                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,
            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.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);
            });
        }
    }

    async _loadFile(file) {
        let info;
        try {
            info = await file.query_info_async(
                Gio.FILE_ATTRIBUTE_STANDARD_CONTENT_TYPE,
                Gio.FileQueryInfoFlags.NONE,
                0,
                this._cancellable);
        } catch (e) {
            this._setLoaded();
            return;
        }

        const contentType = info.get_content_type();
        if (contentType === 'application/xml')
            this._loadAnimation(file);
        else
            this._loadImage(file);
    }

    _load() {
        this._cache = getBackgroundCache();

        this._loadPattern();

        if (!this._file) {
            this._setLoaded();
            return;
        }

        this._loadFile(this._file);
    }
});

let _systemBackground;

var SystemBackground = GObject.registerClass({
    Signals: { 'loaded': {} },
}, class SystemBackground extends Meta.BackgroundActor {
    _init() {
        if (_systemBackground == null) {
            _systemBackground = new Meta.Background({ meta_display: global.display });

            let backgroundColor = DEFAULT_BACKGROUND_COLOR;
            if (imports.misc.desktop.is("ubuntu")) {
                const loginSettings = new Gio.Settings({ schema_id: 'com.ubuntu.login-screen' });
                const bgColor = loginSettings.get_string('background-color');
                const dummyBgActor = new imports.gi.St.Widget({ name: 'lockDialogGroup' });
                if (bgColor)
                    dummyBgActor.set_style(`background-color: ${bgColor};`);
                Main.uiGroup.add_actor(dummyBgActor);
                backgroundColor = dummyBgActor.get_theme_node().get_background_color();
                dummyBgActor.destroy();
            }

            _systemBackground.set_color(backgroundColor);
        }

        super._init({
            meta_display: global.display,
            monitor: 0,
        });
        this.content.background = _systemBackground;

        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');
    }
});

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._onMonitorsChanged.bind(this));

        this._interfaceSettings = new Gio.Settings({ schema_id: INTERFACE_SCHEMA });
    }

    _onMonitorsChanged() {
        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 'bg-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) {
                const colorScheme = this._interfaceSettings.get_enum('color-scheme');
                const uri = this._settings.get_string(
                    colorScheme === GDesktopEnums.ColorScheme.PREFER_DARK
                        ? PICTURE_URI_DARK_KEY
                        : 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,
                layoutManager: this._layoutManager,
                settings: this._settings,
                file,
                style,
            });

            background._changedId = background.connect('bg-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 = GObject.registerClass(
class Animation extends GnomeDesktop.BGSlideShow {
    _init(params) {
        super._init(params);

        this.keyFrameFiles = [];
        this.transitionProgress = 0.0;
        this.transitionDuration = 0.0;
        this.loaded = false;
    }

    // eslint-disable-next-line camelcase
    load_async(cancellable, callback) {
        super.load_async(cancellable, () => {
            this.loaded = true;

            callback?.();
        });
    }

    update(monitor) {
        this.keyFrameFiles = [];

        if (this.get_num_slides() < 1)
            return;

        let [progress, duration, isFixed_, filename1, filename2] =
            this.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));
    }
});

var BackgroundManager = class BackgroundManager {
    constructor(params) {
        params = Params.parse(params, {
            container: null,
            layoutManager: Main.layoutManager,
            monitorIndex: null,
            vignette: false,
            controlPosition: true,
            settingsSchema: BACKGROUND_SCHEMA,
            useContentSize: true,
        });

        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._useContentSize = params.useContentSize;

        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');

        if (Main.layoutManager.screenTransition.visible) {
            oldBackgroundActor.destroy();
            return;
        }

        oldBackgroundActor.ease({
            opacity: 0,
            duration: FADE_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => oldBackgroundActor.destroy(),
        });
    }

    _updateBackgroundActor() {
        if (this._newBackgroundActor) {
            /* Skip displaying existing background queued for load */
            this._newBackgroundActor.destroy();
            this._newBackgroundActor = null;
        }

        let newBackgroundActor = this._createBackgroundActor();

        const oldContent = this.backgroundActor.content;
        const newContent = newBackgroundActor.content;

        newContent.vignette_sharpness = oldContent.vignette_sharpness;
        newContent.brightness = oldContent.brightness;

        newBackgroundActor.visible = this.backgroundActor.visible;

        this._newBackgroundActor = newBackgroundActor;

        const { background } = newBackgroundActor.content;

        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,
            request_mode: this._useContentSize
                ? Clutter.RequestMode.CONTENT_SIZE
                : Clutter.RequestMode.HEIGHT_FOR_WIDTH,
            x_expand: !this._useContentSize,
            y_expand: !this._useContentSize,
        });
        backgroundActor.content.set({
            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);
            this._container.set_child_below_sibling(backgroundActor, null);
        }

        let changeSignalId = background.connect('bg-changed', () => {
            background.disconnect(changeSignalId);
            changeSignalId = null;
            this._updateBackgroundActor();
        });

        let loadedSignalId;
        if (background.isLoaded) {
            GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                this.emit('loaded');
                return GLib.SOURCE_REMOVE;
            });
        } else {
            loadedSignalId = background.connect('loaded', () => {
                background.disconnect(loadedSignalId);
                loadedSignalId = null;
                this.emit('loaded');
            });
        }

        backgroundActor.connect('destroy', () => {
            if (changeSignalId)
                background.disconnect(changeSignalId);

            if (loadedSignalId)
                background.disconnect(loadedSignalId);

            if (backgroundActor.loadedSignalId)
                background.disconnect(backgroundActor.loadedSignalId);
        });

        return backgroundActor;
    }
};
Signals.addSignalMethods(BackgroundManager.prototype);
(uuay)search.js&v// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SearchResultsView */

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

const AppDisplay = imports.ui.appDisplay;
const IconGrid = imports.ui.iconGrid;
const Main = imports.ui.main;
const ParentalControlsManager = imports.misc.parentalControlsManager;
const RemoteSearch = imports.ui.remoteSearch;
const Util = imports.misc.util;

const { Highlighter } = imports.misc.util;

const SEARCH_PROVIDERS_SCHEMA = 'org.gnome.desktop.search-providers';

var MAX_LIST_SEARCH_RESULTS_ROWS = 5;

var MaxWidthBox = GObject.registerClass(
class MaxWidthBox extends St.BoxLayout {
    vfunc_allocate(box) {
        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);
    }
});

var SearchResult = GObject.registerClass(
class SearchResult extends St.Button {
    _init(provider, metaInfo, resultsView) {
        this.provider = provider;
        this.metaInfo = metaInfo;
        this._resultsView = resultsView;

        super._init({
            reactive: true,
            can_focus: true,
            track_hover: true,
        });
    }

    vfunc_clicked() {
        this.activate();
    }

    activate() {
        this.provider.activateResult(this.metaInfo.id, this._resultsView.terms);

        if (this.metaInfo.clipboardText) {
            St.Clipboard.get_default().set_text(
                St.ClipboardType.CLIPBOARD, this.metaInfo.clipboardText);
        }
        Main.overview.toggle();
    }
});

var ListSearchResult = GObject.registerClass(
class ListSearchResult extends SearchResult {
    _init(provider, metaInfo, resultsView) {
        super._init(provider, metaInfo, resultsView);

        this.style_class = 'list-search-result';

        let content = new St.BoxLayout({
            style_class: 'list-search-result-content',
            vertical: false,
            x_align: Clutter.ActorAlign.START,
            x_expand: true,
            y_expand: true,
        });
        this.set_child(content);

        let titleBox = new St.BoxLayout({
            style_class: 'list-search-result-title',
            y_align: Clutter.ActorAlign.CENTER,
        });

        content.add_child(titleBox);

        // 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'],
            y_align: Clutter.ActorAlign.CENTER,
        });
        titleBox.add_child(title);

        this.label_actor = title;

        if (this.metaInfo['description']) {
            this._descriptionLabel = new St.Label({
                style_class: 'list-search-result-description',
                y_align: Clutter.ActorAlign.CENTER,
            });
            content.add_child(this._descriptionLabel);

            this._resultsView.connectObject(
                'terms-changed', this._highlightTerms.bind(this), this);

            this._highlightTerms();
        }
    }

    get ICON_SIZE() {
        return 24;
    }

    _highlightTerms() {
        let markup = this._resultsView.highlightTerms(this.metaInfo['description'].split('\n')[0]);
        this._descriptionLabel.clutter_text.set_markup(markup);
    }
});

var GridSearchResult = GObject.registerClass(
class GridSearchResult extends SearchResult {
    _init(provider, metaInfo, resultsView) {
        super._init(provider, metaInfo, resultsView);

        this.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,
            x_align: Clutter.ActorAlign.START,
            x_expand: true,
            y_expand: true,
        });
        this.set_child(content);
        this.label_actor = this.icon.label;
    }
});

var SearchResultsBase = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
    Properties: {
        'focus-child': GObject.ParamSpec.object(
            'focus-child', 'focus-child', 'focus-child',
            GObject.ParamFlags.READABLE,
            Clutter.Actor.$gtype),
    },
}, class SearchResultsBase extends St.BoxLayout {
    _init(provider, resultsView) {
        super._init({ style_class: 'search-section', vertical: true });

        this.provider = provider;
        this._resultsView = resultsView;

        this._terms = [];
        this._focusChild = null;

        this._resultDisplayBin = new St.Bin();
        this.add_child(this._resultDisplayBin);

        let separator = new St.Widget({ style_class: 'search-section-separator' });
        this.add(separator);

        this._resultDisplays = {};

        this._cancellable = new Gio.Cancellable();

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

    _onDestroy() {
        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].destroy();
        this._resultDisplays = {};
        this._clearResultDisplay();
        this.hide();
    }

    get focusChild() {
        return this._focusChild;
    }

    _keyFocusIn(actor) {
        if (this._focusChild == actor)
            return;
        this._focusChild = actor;
        this.notify('focus-child');
    }

    _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.resultsMetasInProgress = true;
            this.provider.getResultMetas(metasNeeded, metas => {
                this.provider.resultsMetasInProgress = this._cancellable.is_cancelled();
                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('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.hide();
            callback();
        } else {
            let maxResults = this._getMaxDisplayedResults();
            let results = maxResults > -1
                ? this.provider.filterResults(providerResults, maxResults)
                : providerResults;
            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.hide();
                this._clearResultDisplay();
                results.forEach(resultId => {
                    this._addItem(this._resultDisplays[resultId]);
                });
                this._setMoreCount(this.provider.canLaunchSearch ? moreCount : 0);
                this.show();
                callback();
            });
        }
    }
});

var ListSearchResults = GObject.registerClass(
class ListSearchResults extends SearchResultsBase {
    _init(provider, resultsView) {
        super._init(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_child(this.providerInfo);

        this._content = new St.BoxLayout({
            style_class: 'list-search-results',
            vertical: true,
            x_expand: true,
        });
        this._container.add_child(this._content);

        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) ||
               new ListSearchResult(this.provider, meta, this._resultsView);
    }

    _addItem(display) {
        this._content.add_actor(display);
    }

    getFirstResult() {
        if (this._content.get_n_children() > 0)
            return this._content.get_child_at_index(0);
        else
            return null;
    }
});

var GridSearchResultsLayout = GObject.registerClass({
    Properties: {
        'spacing': GObject.ParamSpec.int('spacing', 'Spacing', 'Spacing',
            GObject.ParamFlags.READWRITE, 0, GLib.MAXINT32, 0),
    },
}, class GridSearchResultsLayout extends Clutter.LayoutManager {
    _init() {
        super._init();
        this._spacing = 0;
    }

    vfunc_set_container(container) {
        this._container = container;
    }

    vfunc_get_preferred_width(container, forHeight) {
        let minWidth = 0;
        let natWidth = 0;
        let first = true;

        for (let child of container) {
            if (!child.visible)
                continue;

            const [childMinWidth, childNatWidth] = child.get_preferred_width(forHeight);

            minWidth = Math.max(minWidth, childMinWidth);
            natWidth += childNatWidth;

            if (first)
                first = false;
            else
                natWidth += this._spacing;
        }

        return [minWidth, natWidth];
    }

    vfunc_get_preferred_height(container, forWidth) {
        let minHeight = 0;
        let natHeight = 0;

        for (let child of container) {
            if (!child.visible)
                continue;

            const [childMinHeight, childNatHeight] = child.get_preferred_height(forWidth);

            minHeight = Math.max(minHeight, childMinHeight);
            natHeight = Math.max(natHeight, childNatHeight);
        }

        return [minHeight, natHeight];
    }

    vfunc_allocate(container, box) {
        const width = box.get_width();

        const childBox = new Clutter.ActorBox();
        childBox.x1 = 0;
        childBox.y1 = 0;

        let first = true;
        for (let child of container) {
            if (!child.visible)
                continue;

            if (first)
                first = false;
            else
                childBox.x1 += this._spacing;

            const [childWidth] = child.get_preferred_width(-1);
            const [childHeight] = child.get_preferred_height(-1);

            if (childBox.x1 + childWidth <= width)
                childBox.set_size(childWidth, childHeight);
            else
                childBox.set_size(0, 0);

            child.allocate(childBox);
            child.can_focus = childBox.get_area() > 0;

            childBox.x1 += childWidth;
        }
    }

    columnsForWidth(width) {
        if (!this._container)
            return -1;

        const [minWidth] = this.get_preferred_width(this._container, -1);

        if (minWidth === 0)
            return -1;

        let nCols = 0;
        while (width > minWidth) {
            width -= minWidth;
            if (nCols > 0)
                width -= this._spacing;
            nCols++;
        }

        return nCols;
    }

    get spacing() {
        return this._spacing;
    }

    set spacing(v) {
        if (this._spacing === v)
            return;
        this._spacing = v;
        this.layout_changed();
    }
});

var GridSearchResults = GObject.registerClass(
class GridSearchResults extends SearchResultsBase {
    _init(provider, resultsView) {
        super._init(provider, resultsView);

        this._grid = new St.Widget({ style_class: 'grid-search-results' });
        this._grid.layout_manager = new GridSearchResultsLayout();

        this._grid.connect('style-changed', () => {
            const node = this._grid.get_theme_node();
            this._grid.layout_manager.spacing = node.get_length('spacing');
        });

        this._resultDisplayBin.set_child(new St.Bin({
            child: this._grid,
            x_align: Clutter.ActorAlign.CENTER,
        }));
    }

    _onDestroy() {
        if (this._updateSearchLater) {
            Meta.later_remove(this._updateSearchLater);
            delete this._updateSearchLater;
        }

        super._onDestroy();
    }

    updateSearch(...args) {
        if (this._notifyAllocationId)
            this.disconnect(this._notifyAllocationId);
        if (this._updateSearchLater) {
            Meta.later_remove(this._updateSearchLater);
            delete this._updateSearchLater;
        }

        // Make sure the maximum number of results calculated by
        // _getMaxDisplayedResults() is updated after width changes.
        this._notifyAllocationId = this.connect('notify::allocation', () => {
            if (this._updateSearchLater)
                return;
            this._updateSearchLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                delete this._updateSearchLater;
                super.updateSearch(...args);
                return GLib.SOURCE_REMOVE;
            });
        });

        super.updateSearch(...args);
    }

    _getMaxDisplayedResults() {
        let width = this.allocation.get_width();
        if (width == 0)
            return -1;

        return this._grid.layout_manager.columnsForWidth(width);
    }

    _clearResultDisplay() {
        this._grid.remove_all_children();
    }

    _createResultDisplay(meta) {
        return super._createResultDisplay(meta) ||
               new GridSearchResult(this.provider, meta, this._resultsView);
    }

    _addItem(display) {
        this._grid.add_child(display);
    }

    getFirstResult() {
        for (let child of this._grid) {
            if (child.visible)
                return child;
        }
        return null;
    }
});

var SearchResultsView = GObject.registerClass({
    Signals: { 'terms-changed': {} },
}, class SearchResultsView extends St.BoxLayout {
    _init() {
        super._init({ name: 'searchResults', vertical: true });

        this._parentalControlsManager = ParentalControlsManager.getDefault();
        this._parentalControlsManager.connect('app-filter-changed', this._reloadRemoteProviders.bind(this));

        this._content = new MaxWidthBox({
            name: 'searchResultsContent',
            vertical: true,
            x_expand: true,
        });

        this._scrollView = new St.ScrollView({
            overlay_scrollbars: true,
            style_class: 'search-display vfade',
            x_expand: true,
            y_expand: true,
        });
        this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        this._scrollView.add_actor(this._content);

        let action = new Clutter.PanAction({ interpolate: true });
        action.connect('pan', this._onPan.bind(this));
        this._scrollView.add_action(action);

        this.add_child(this._scrollView);

        this._statusText = new St.Label({
            style_class: 'search-statustext',
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._statusBin = new St.Bin({ y_expand: true });
        this.add_child(this._statusBin);
        this._statusBin.add_actor(this._statusText);

        this._highlightDefault = false;
        this._defaultResult = null;
        this._startingSearch = false;

        this._terms = [];
        this._results = {};

        this._providers = [];

        this._highlighter = new Highlighter();

        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._searchCancelCancellable = new Gio.Cancellable();
        this._cancellable.connect(() => {
            this._cancelSearchProviderRequest();
        });

        this._registerProvider(new AppDisplay.AppSearchProvider());

        let appSystem = Shell.AppSystem.get_default();
        appSystem.connect('installed-changed', this._reloadRemoteProviders.bind(this));
        this._reloadRemoteProviders();
    }

    get terms() {
        return this._terms;
    }

    _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;

        // Filter out unwanted providers.
        if (provider.appInfo && !this._parentalControlsManager.shouldShowApp(provider.appInfo))
            return;

        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;
        }
    }

    _cancelSearchProviderRequest() {
        if (this._terms.length != 0 || this._searchCancelTimeoutId)
            return;

        this._searchCancelTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 100, () => {
            this._providers.forEach(provider => {
                if (provider.isRemoteProvider &&
                    (provider.searchInProgress || provider.resultsMetasInProgress)) {
                    provider.XUbuntuCancel(this._searchCancelCancellable, () => {
                        provider.searchInProgress = false;
                        provider.resultsMetasInProgress = false;
                    });
                }
            });

            delete this._searchCancelTimeoutId;
            return GLib.SOURCE_REMOVE;
        });
    }

    _reset() {
        this._terms = [];
        this._results = {};
        this._clearDisplay();
        this._clearSearchTimeout();
        this._cancelSearchProviderRequest();
        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._searchCancelCancellable.cancel();
        this._searchCancelCancellable.reset();
        if (this._searchCancelTimeoutId) {
            GLib.source_remove(this._searchCancelTimeoutId);
            delete this._searchCancelTimeoutId;
        }

        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));

        this._highlighter = new Highlighter(this._terms);

        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.height) * adjustment.page_size;
        return false;
    }

    _focusChildChanged(provider) {
        Util.ensureActorVisibleInScrollView(this._scrollView, provider.focusChild);
    }

    _ensureProviderDisplay(provider) {
        if (provider.display)
            return;

        let providerDisplay;
        if (provider.appInfo)
            providerDisplay = new ListSearchResults(provider, this);
        else
            providerDisplay = new GridSearchResults(provider, this);

        providerDisplay.connect('notify::focus-child', this._focusChildChanged.bind(this));
        providerDisplay.hide();
        this._content.add(providerDisplay);
        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.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.popup_menu();
    }

    navigateFocus(direction) {
        let rtl = this.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.navigate_focus(null, direction, false);
            return;
        }

        const from = this._defaultResult ?? null;
        this.navigate_focus(from, direction, false);
    }

    _setSelected(result, selected) {
        if (!result)
            return;

        if (selected) {
            result.add_style_pseudo_class('selected');
            Util.ensureActorVisibleInScrollView(this._scrollView, result);
        } else {
            result.remove_style_pseudo_class('selected');
        }
    }

    highlightTerms(description) {
        if (!description)
            return '';

        return this._highlighter.highlight(description);
    }
});

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,
            y_align: Clutter.ActorAlign.START,
        });

        this._content = new St.BoxLayout({
            vertical: false,
            style_class: 'list-search-provider-content',
        });
        this.set_child(this._content);

        const icon = new St.Icon({
            icon_size: this.PROVIDER_ICON_SIZE,
            gicon: provider.appInfo.get_icon(),
        });

        const detailsBox = new St.BoxLayout({
            style_class: 'list-search-provider-details',
            vertical: true,
            x_expand: true,
        });

        const 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)accessDialog.js�/* exported AccessDialogDBus */
const { Clutter, Gio, GLib, GObject, Pango, Shell, St } = 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 = GObject.registerClass(
class AccessDialog extends ModalDialog.ModalDialog {
    _init(invocation, handle, title, description, body, options) {
        super._init({ 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, description, body, options);
    }

    _buildLayout(title, description, 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 choices = options['choices'] || [];

        let content = new Dialog.MessageDialogContent({ title, description });
        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.checked = selected == "true";
            content.add_child(check);

            this._choices.set(id, check);
        }

        let bodyLabel = new St.Label({
            text: body,
            x_align: Clutter.ActorAlign.CENTER,
        });
        bodyLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        bodyLabel.clutter_text.line_wrap = true;
        content.add_child(bodyLabel);

        this.addButton({
            label: denyLabel,
            action: () => this._sendResponse(DialogResponse.CANCEL),
            key: Clutter.KEY_Escape,
        });
        this.addButton({
            label: grantLabel,
            action: () => this._sendResponse(DialogResponse.OK),
        });
    }

    open() {
        if (!super.open())
            return false;

        let connection = this._invocation.get_connection();
        this._requestExported = this._request.export(connection, this._handle);
        return true;
    }

    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.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.gnome.Shell.Portal', 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, description, 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, description, body, options);
        dialog.open();

        dialog.connect('closed', () => (this._accessDialog = null));

        this._accessDialog = dialog;
    }
};
(uuay)lookingGlass.jsx�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported LookingGlass */

const {
    Clutter, Cogl, Gio, GLib, GObject, Graphene, Meta, Pango, Shell, St,
} = imports.gi;
const Signals = imports.signals;
const System = imports.system;

const History = imports.misc.history;
const ExtensionUtils = imports.misc.extensionUtils;
const PopupMenu = imports.ui.popupMenu;
const ShellEntry = imports.ui.shellEntry;
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; ' +
                    /* 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 = 200;
var AUTO_COMPLETE_GLOBAL_KEYWORDS = _getAutoCompleteGlobalKeywords();

const LG_ANIMATION_TIME = 500;

const CLUTTER_DEBUG_FLAG_CATEGORIES = new Map([
    // Paint debugging can easily result in a non-responsive session
    ['DebugFlag', { argPos: 0, exclude: ['PAINT'] }],
    ['DrawDebugFlag', { argPos: 1, exclude: [] }],
    // Exluded due to the only current option likely to result in shooting ones
    // foot
    // ['PickDebugFlag', { argPos: 2, exclude: [] }],
]);

function _getAutoCompleteGlobalKeywords() {
    const keywords = ['true', 'false', 'null', 'new'];
    // Don't add the private properties of globalThis (i.e., ones starting with '_')
    const windowProperties = Object.getOwnPropertyNames(globalThis).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.KEY_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,
                    attrHead,
                });
            } else {
                this._processCompletionRequest({
                    tabType: 'single',
                    completions,
                    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 = GObject.registerClass({
    Signals: { 'selection': { param_types: [Clutter.Actor.$gtype] } },
}, class Notebook extends St.BoxLayout {
    _init() {
        super._init({
            vertical: true,
            y_expand: true,
        });

        this.tabControls = new St.BoxLayout({ style_class: 'labels' });

        this._selectedIndex = -1;
        this._tabs = [];
    }

    appendPage(name, child) {
        const 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_child(label);
        this.tabControls.add(labelBox);

        let scrollview = new St.ScrollView({ y_expand: true });
        scrollview.get_hscroll_bar().hide();
        scrollview.add_actor(child);

        const tabData = {
            child,
            labelBox,
            label,
            scrollView: scrollview,
            _scrollToBottom: false,
        };
        this._tabs.push(tabData);
        scrollview.hide();
        this.add_child(scrollview);

        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.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);
    }
});

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 = GObject.registerClass(
class ObjLink extends St.Button {
    _init(lookingGlass, o, title) {
        let text;
        if (title)
            text = title;
        else
            text = objectToString(o);
        text = GLib.markup_escape_text(text, -1);

        super._init({
            reactive: true,
            track_hover: true,
            style_class: 'shell-link',
            label: text,
            x_align: Clutter.ActorAlign.START,
        });
        this.get_child().single_line_mode = true;

        this._obj = o;
        this._lookingGlass = lookingGlass;
    }

    vfunc_clicked() {
        this._lookingGlass.inspectObject(this._obj, this);
    }
});

var Result = GObject.registerClass(
class Result extends St.BoxLayout {
    _init(lookingGlass, command, o, index) {
        super._init({ vertical: true });

        this.index = index;
        this.o = o;

        this._lookingGlass = lookingGlass;

        let cmdTxt = new St.Label({ text: command });
        cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        this.add(cmdTxt);
        let box = new St.BoxLayout({});
        this.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);
    }
});

var WindowList = GObject.registerClass({
}, class WindowList extends St.BoxLayout {
    _init(lookingGlass) {
        super._init({ name: 'Windows', vertical: true, style: 'spacing: 8px' });
        let tracker = Shell.WindowTracker.get_default();
        this._updateId = Main.initializeDeferredWork(this, 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() {
        if (!this._lookingGlass.isOpen)
            return;

        this.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.add(box);
            let windowLink = new ObjLink(this._lookingGlass, metaWindow, metaWindow.title);
            box.add_child(windowLink);
            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_child(new St.Label({ text: 'app: ' }));
                let appLink = new ObjLink(this._lookingGlass, app, app.get_id());
                propBox.add_child(appLink);
                propBox.add_child(icon);
            } else {
                propsBox.add(new St.Label({ text: '<untracked>' }));
            }
        }
    }

    update() {
        this._updateWindowList();
    }
});

var ObjInspector = GObject.registerClass(
class ObjInspector extends St.ScrollView {
    _init(lookingGlass) {
        super._init({
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
        });

        this._obj = null;
        this._previousObj = null;

        this._parentList = [];

        this.get_hscroll_bar().hide();
        this._container = new St.BoxLayout({
            name: 'LookingGlassPropertyInspector',
            style_class: 'lg-dialog',
            vertical: true,
            x_expand: true,
            y_expand: true,
        });
        this.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: ${typeof obj}: ${objectToString(obj)}`,
            x_expand: true,
        });
        label.single_line_mode = true;
        hbox.add_child(label);
        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 link;
                try {
                    let prop = obj[propName];
                    link = new ObjLink(this._lookingGlass, prop);
                } catch (e) {
                    link = new St.Label({ text: '<error>' });
                }
                let box = new St.BoxLayout();
                box.add(new St.Label({ text: `${propName}: ` }));
                box.add(link);
                this._container.add_actor(box);
            }
        }
    }

    open(sourceActor) {
        if (this._open)
            return;

        const grab = Main.pushModal(this, { actionMode: Shell.ActionMode.LOOKING_GLASS });
        if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
            Main.popModal(grab);
            return;
        }

        this._grab = grab;
        this._previousObj = null;
        this._open = true;
        this.show();
        if (sourceActor) {
            this.set_scale(0, 0);
            this.ease({
                scale_x: 1,
                scale_y: 1,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                duration: 200,
            });
        } else {
            this.set_scale(1, 1);
        }
    }

    close() {
        if (!this._open)
            return;
        Main.popModal(this._grab);
        this._grab = null;
        this._open = false;
        this.hide();
        this._previousObj = null;
        this._obj = null;
    }

    vfunc_key_press_event(keyPressEvent) {
        const symbol = keyPressEvent.keyval;
        if (symbol === Clutter.KEY_Escape) {
            this.close();
            return Clutter.EVENT_STOP;
        }
        return super.vfunc_key_press_event(keyPressEvent);
    }

    _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 {
    _init() {
        super._init();
        this._pipeline = null;
    }

    vfunc_paint_node(node, paintContext) {
        let actor = this.get_actor();

        const actorNode = new Clutter.ActorNode(actor, -1);
        node.add_child(actorNode);

        if (!this._pipeline) {
            const framebuffer = paintContext.get_framebuffer();
            const coglContext = framebuffer.get_context();

            let color = new Cogl.Color();
            color.init_from_4ub(0xff, 0, 0, 0xc4);

            this._pipeline = new Cogl.Pipeline(coglContext);
            this._pipeline.set_color(color);
        }

        let alloc = actor.get_allocation_box();
        let width = 2;

        const pipelineNode = new Clutter.PipelineNode(this._pipeline);
        pipelineNode.set_name('Red Border');
        node.add_child(pipelineNode);

        const box = new Clutter.ActorBox();

        // clockwise order
        box.set_origin(0, 0);
        box.set_size(alloc.get_width(), width);
        pipelineNode.add_rectangle(box);

        box.set_origin(alloc.get_width() - width, width);
        box.set_size(width, alloc.get_height() - width);
        pipelineNode.add_rectangle(box);

        box.set_origin(0, alloc.get_height() - width);
        box.set_size(alloc.get_width() - width, width);
        pipelineNode.add_rectangle(box);

        box.set_origin(0, width);
        box.set_size(width, alloc.get_height() - width * 2);
        pipelineNode.add_rectangle(box);
    }
});

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);

        const eventHandler = new St.BoxLayout({
            name: 'LookingGlassDialog',
            vertical: false,
            reactive: true,
        });
        this._eventHandler = eventHandler;
        this.add_actor(eventHandler);
        this._displayText = new St.Label({ x_expand: true });
        eventHandler.add_child(this._displayText);

        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));

        this._grab = global.stage.grab(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) {
        this.set_allocation(box);

        if (!this._eventHandler)
            return;

        let primary = Main.layoutManager.primaryMonitor;

        let [, , 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);
    }

    _close() {
        if (this._grab) {
            this._grab.dismiss();
            this._grab = null;
        }
        this._eventHandler.destroy();
        this._eventHandler = null;
        this.emit('closed');
    }

    _onKeyPressEvent(actor, event) {
        if (event.get_key_symbol() === Clutter.KEY_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 = GObject.registerClass({
}, class Extensions extends St.BoxLayout {
    _init(lookingGlass) {
        super._init({ vertical: true, name: 'lookingGlassExtensions' });

        this._lookingGlass = lookingGlass;
        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.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++;
        const { name } = extension.metadata;
        const pos = [...this._extensionsList].findIndex(
            dsp => dsp._extension.metadata.name.localeCompare(name) > 0);
        this._extensionsList.insert_child_at_index(extensionDisplay, pos);
    }

    _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 });
        box._extension = extension;
        let name = new St.Label({
            style_class: 'lg-extension-name',
            text: extension.metadata.name,
            x_expand: true,
        });
        box.add_child(name);
        let description = new St.Label({
            style_class: 'lg-extension-description',
            text: extension.metadata.description || 'No description',
            x_expand: true,
        });
        box.add_child(description);

        let metaBox = new St.BoxLayout({ style_class: 'lg-extension-meta' });
        box.add(metaBox);
        const state = new St.Label({
            style_class: 'lg-extension-state',
            text: this._stateToString(extension.state),
        });
        metaBox.add(state);

        const 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) {
            const 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);
        }

        const 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 ActorLink = GObject.registerClass({
    Signals: {
        'inspect-actor': {},
    },
}, class ActorLink extends St.Button {
    _init(actor) {
        this._arrow = new St.Icon({
            icon_name: 'pan-end-symbolic',
            icon_size: 8,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
        });

        const label = new St.Label({
            text: actor.toString(),
            x_align: Clutter.ActorAlign.START,
        });

        const inspectButton = new St.Button({
            child: new St.Icon({
                icon_name: 'insert-object-symbolic',
                icon_size: 12,
                y_align: Clutter.ActorAlign.CENTER,
            }),
            reactive: true,
            x_expand: true,
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.CENTER,
        });
        inspectButton.connect('clicked', () => this.emit('inspect-actor'));

        const box = new St.BoxLayout();
        box.add_child(this._arrow);
        box.add_child(label);
        box.add_child(inspectButton);

        super._init({
            reactive: true,
            track_hover: true,
            toggle_mode: true,
            style_class: 'actor-link',
            child: box,
            x_align: Clutter.ActorAlign.START,
        });

        this._actor = actor;
    }

    vfunc_clicked() {
        this._arrow.ease({
            rotation_angle_z: this.checked ? 90 : 0,
            duration: 250,
        });
    }
});

var ActorTreeViewer = GObject.registerClass(
class ActorTreeViewer extends St.BoxLayout {
    _init(lookingGlass) {
        super._init();

        this._lookingGlass = lookingGlass;
        this._actorData = new Map();
    }

    _showActorChildren(actor) {
        const data = this._actorData.get(actor);
        if (!data || data.visible)
            return;

        data.visible = true;
        data.actorAddedId = actor.connect('actor-added', (container, child) => {
            this._addActor(data.children, child);
        });
        data.actorRemovedId = actor.connect('actor-removed', (container, child) => {
            this._removeActor(child);
        });

        for (let child of actor)
            this._addActor(data.children, child);
    }

    _hideActorChildren(actor) {
        const data = this._actorData.get(actor);
        if (!data || !data.visible)
            return;

        for (let child of actor)
            this._removeActor(child);

        data.visible = false;
        if (data.actorAddedId > 0) {
            actor.disconnect(data.actorAddedId);
            data.actorAddedId = 0;
        }
        if (data.actorRemovedId > 0) {
            actor.disconnect(data.actorRemovedId);
            data.actorRemovedId = 0;
        }
        data.children.remove_all_children();
    }

    _addActor(container, actor) {
        if (this._actorData.has(actor))
            return;

        if (actor === this._lookingGlass)
            return;

        const button = new ActorLink(actor);
        button.connect('notify::checked', () => {
            this._lookingGlass.setBorderPaintTarget(actor);
            if (button.checked)
                this._showActorChildren(actor);
            else
                this._hideActorChildren(actor);
        });
        button.connect('inspect-actor', () => {
            this._lookingGlass.inspectObject(actor, button);
        });

        const mainContainer = new St.BoxLayout({ vertical: true });
        const childrenContainer = new St.BoxLayout({
            vertical: true,
            style: 'padding: 0 0 0 18px',
        });

        mainContainer.add_child(button);
        mainContainer.add_child(childrenContainer);

        this._actorData.set(actor, {
            button,
            container: mainContainer,
            children: childrenContainer,
            visible: false,
            actorAddedId: 0,
            actorRemovedId: 0,
            actorDestroyedId: actor.connect('destroy', () => this._removeActor(actor)),
        });

        let belowChild = null;
        const nextSibling = actor.get_next_sibling();
        if (nextSibling && this._actorData.has(nextSibling))
            belowChild = this._actorData.get(nextSibling).container;

        container.insert_child_above(mainContainer, belowChild);
    }

    _removeActor(actor) {
        const data = this._actorData.get(actor);
        if (!data)
            return;

        for (let child of actor)
            this._removeActor(child);

        if (data.actorAddedId > 0) {
            actor.disconnect(data.actorAddedId);
            data.actorAddedId = 0;
        }
        if (data.actorRemovedId > 0) {
            actor.disconnect(data.actorRemovedId);
            data.actorRemovedId = 0;
        }
        if (data.actorDestroyedId > 0) {
            actor.disconnect(data.actorDestroyedId);
            data.actorDestroyedId = 0;
        }
        data.container.destroy();
        this._actorData.delete(actor);
    }

    vfunc_map() {
        super.vfunc_map();
        this._addActor(this, global.stage);
    }

    vfunc_unmap() {
        super.vfunc_unmap();
        this._removeActor(global.stage);
    }
});

var DebugFlag = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
}, class DebugFlag extends St.Button {
    _init(label) {
        const box = new St.BoxLayout();

        const flagLabel = new St.Label({
            text: label,
            x_expand: true,
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.CENTER,
        });
        box.add_child(flagLabel);

        this._flagSwitch = new PopupMenu.Switch(false);
        this._stateHandler = this._flagSwitch.connect('notify::state', () => {
            if (this._flagSwitch.state)
                this._enable();
            else
                this._disable();
        });

        // Update state whenever the switch is mapped, because most debug flags
        // don't have a way of notifying us of changes.
        this._flagSwitch.connect('notify::mapped', () => {
            if (!this._flagSwitch.is_mapped())
                return;

            const state = this._isEnabled();
            if (state === this._flagSwitch.state)
                return;

            this._flagSwitch.block_signal_handler(this._stateHandler);
            this._flagSwitch.state = state;
            this._flagSwitch.unblock_signal_handler(this._stateHandler);
        });

        box.add_child(this._flagSwitch);

        super._init({
            style_class: 'lg-debug-flag-button',
            can_focus: true,
            toggleMode: true,
            child: box,
            label_actor: flagLabel,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this.connect('clicked', () => this._flagSwitch.toggle());
    }

    _isEnabled() {
        throw new Error('Method not implemented');
    }

    _enable() {
        throw new Error('Method not implemented');
    }

    _disable() {
        throw new Error('Method not implemented');
    }
});


var ClutterDebugFlag = GObject.registerClass(
class ClutterDebugFlag extends DebugFlag {
    _init(categoryName, flagName) {
        super._init(flagName);

        this._argPos = CLUTTER_DEBUG_FLAG_CATEGORIES.get(categoryName).argPos;
        this._enumValue = Clutter[categoryName][flagName];
    }

    _isEnabled() {
        const enabledFlags = Meta.get_clutter_debug_flags();
        return !!(enabledFlags[this._argPos] & this._enumValue);
    }

    _getArgs() {
        const args = [0, 0, 0];
        args[this._argPos] = this._enumValue;
        return args;
    }

    _enable() {
        Meta.add_clutter_debug_flags(...this._getArgs());
    }

    _disable() {
        Meta.remove_clutter_debug_flags(...this._getArgs());
    }
});

var MutterPaintDebugFlag = GObject.registerClass(
class MutterPaintDebugFlag extends DebugFlag {
    _init(flagName) {
        super._init(flagName);

        this._enumValue = Meta.DebugPaintFlag[flagName];
    }

    _isEnabled() {
        return !!(Meta.get_debug_paint_flags() & this._enumValue);
    }

    _enable() {
        Meta.add_debug_paint_flag(this._enumValue);
    }

    _disable() {
        Meta.remove_debug_paint_flag(this._enumValue);
    }
});

var MutterTopicDebugFlag = GObject.registerClass(
class MutterTopicDebugFlag extends DebugFlag {
    _init(flagName) {
        super._init(flagName);

        this._enumValue = Meta.DebugTopic[flagName];
    }

    _isEnabled() {
        return Meta.is_topic_enabled(this._enumValue);
    }

    _enable() {
        Meta.add_verbose_topic(this._enumValue);
    }

    _disable() {
        Meta.remove_verbose_topic(this._enumValue);
    }
});

var UnsafeModeDebugFlag = GObject.registerClass(
class UnsafeModeDebugFlag extends DebugFlag {
    _init() {
        super._init('unsafe-mode');
    }

    _isEnabled() {
        return global.context.unsafe_mode;
    }

    _enable() {
        global.context.unsafe_mode = true;
    }

    _disable() {
        global.context.unsafe_mode = false;
    }
});

var DebugFlags = GObject.registerClass(
class DebugFlags extends St.BoxLayout {
    _init() {
        super._init({
            name: 'lookingGlassDebugFlags',
            vertical: true,
            x_align: Clutter.ActorAlign.CENTER,
        });

        // Clutter debug flags
        for (const [categoryName, props] of CLUTTER_DEBUG_FLAG_CATEGORIES.entries()) {
            this._addHeader(`Clutter${categoryName}`);
            for (const flagName of this._getFlagNames(Clutter[categoryName])) {
                if (props.exclude.includes(flagName))
                    continue;
                this.add_child(new ClutterDebugFlag(categoryName, flagName));
            }
        }

        // Meta paint flags
        this._addHeader('MetaDebugPaintFlag');
        for (const flagName of this._getFlagNames(Meta.DebugPaintFlag))
            this.add_child(new MutterPaintDebugFlag(flagName));

        // Meta debug topics
        this._addHeader('MetaDebugTopic');
        for (const flagName of this._getFlagNames(Meta.DebugTopic))
            this.add_child(new MutterTopicDebugFlag(flagName));

        // MetaContext::unsafe-mode
        this._addHeader('MetaContext');
        this.add_child(new UnsafeModeDebugFlag());
    }

    _addHeader(title) {
        const header = new St.Label({
            text: title,
            style_class: 'lg-debug-flags-header',
            x_align: Clutter.ActorAlign.START,
        });
        this.add_child(header);
    }

    *_getFlagNames(enumObject) {
        for (const flagName of Object.getOwnPropertyNames(enumObject)) {
            if (typeof enumObject[flagName] !== 'number')
                continue;

            if (enumObject[flagName] <= 0)
                continue;

            yield flagName;
        }
    }
});


var LookingGlass = GObject.registerClass(
class LookingGlass extends St.BoxLayout {
    _init() {
        super._init({
            name: 'LookingGlassDialog',
            style_class: 'lg-dialog',
            vertical: true,
            visible: false,
            reactive: true,
        });

        this._borderPaintTarget = null;
        this._redBorderEffect = new RedBorderEffect();

        this._open = false;

        this._it = null;
        this._offset = 0;

        // Sort of magic, but...eh.
        this._maxItems = 150;

        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);
        Main.uiGroup.set_child_below_sibling(this,
                                             Main.layoutManager.panelBox);
        Main.layoutManager.panelBox.connect('notify::allocation',
                                            this._queueResize.bind(this));
        Main.layoutManager.keyboardBox.connect('notify::allocation',
                                               this._queueResize.bind(this));

        this._objInspector = new ObjInspector(this);
        Main.uiGroup.add_actor(this._objInspector);
        this._objInspector.hide();

        let toolbar = new St.BoxLayout({ name: 'Toolbar' });
        this.add_actor(toolbar);
        const inspectButton = new St.Button({
            style_class: 'lg-toolbar-button',
            child: new St.Icon({
                icon_name: 'find-location-symbolic',
            }),
        });
        toolbar.add_actor(inspectButton);
        inspectButton.connect('clicked', () => {
            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.show();
                global.stage.set_key_focus(this._entry);
            });
            this.hide();
            return Clutter.EVENT_STOP;
        });

        const gcButton = new St.Button({
            style_class: 'lg-toolbar-button',
            child: new St.Icon({
                icon_name: 'user-trash-full-symbolic',
            }),
        });
        toolbar.add_actor(gcButton);
        gcButton.connect('clicked', () => {
            gcButton.child.icon_name = 'user-trash-symbolic';
            System.gc();
            this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
                gcButton.child.icon_name = 'user-trash-full-symbolic';
                this._timeoutId = 0;
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(
                this._timeoutId,
                '[gnome-shell] gcButton.child.icon_name = \'user-trash-full-symbolic\''
            );
            return Clutter.EVENT_PROPAGATE;
        });

        let notebook = new Notebook();
        this._notebook = notebook;
        this.add_child(notebook);

        let emptyBox = new St.Bin({ x_expand: true });
        toolbar.add_child(emptyBox);
        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,
            y_expand: true,
        });
        this._evalBox.add_child(this._resultsArea);

        this._entryArea = new St.BoxLayout({
            name: 'EntryArea',
            y_align: Clutter.ActorAlign.END,
        });
        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,
            x_expand: true,
        });
        ShellEntry.addContextMenu(this._entry);
        this._entryArea.add_child(this._entry);

        this._windowList = new WindowList(this);
        notebook.appendPage('Windows', this._windowList);

        this._extensions = new Extensions(this);
        notebook.appendPage('Extensions', this._extensions);

        this._actorTreeViewer = new ActorTreeViewer(this);
        notebook.appendPage('Actors', this._actorTreeViewer);

        this._debugFlags = new DebugFlags();
        notebook.appendPage('Flags', this._debugFlags);

        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', ' ');
            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();
    }

    vfunc_captured_event(event) {
        if (Main.keyboard.maybeHandleEvent(event))
            return Clutter.EVENT_STOP;

        return Clutter.EVENT_PROPAGATE;
    }

    _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.
        let size = fontDesc.get_size() / 1024.;
        let unit = fontDesc.get_size_is_absolute() ? 'px' : 'pt';
        this.style = `
            font-size: ${size}${unit};
            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._resultsArea.get_n_children() + this._offset;
        let result = new Result(this, CHEVRON + command, obj, index);
        this._resultsArea.add(result);
        if (obj instanceof Clutter.Actor)
            this.setBorderPaintTarget(obj);

        if (this._resultsArea.get_n_children() > this._maxItems) {
            this._resultsArea.get_first_child().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 set when animating
        this._completionActor.set_height(-1);
        let [, 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 {
            let settings = St.Settings.get();
            let duration = AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / settings.slow_down_factor;
            this._completionActor.show();
            this._completionActor.remove_all_transitions();
            this._completionActor.ease({
                height: naturalHeight,
                opacity: 255,
                duration,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        }
    }

    _hideCompletions() {
        if (this._completionActor) {
            let settings = St.Settings.get();
            let duration = AUTO_COMPLETE_SHOW_COMPLETION_ANIMATION_DURATION / settings.slow_down_factor;
            this._completionActor.remove_all_transitions();
            this._completionActor.ease({
                height: 0,
                opacity: 0,
                duration,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._completionActor.hide();
                },
            });
        }
    }

    _evaluate(command) {
        command = this._history.addItem(command); // trims command
        if (!command)
            return;

        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) {
        try {
            return this._resultsArea.get_child_at_index(idx - this._offset).o;
        } catch (e) {
            throw new Error(`Unknown result at index ${idx}`);
        }
    }

    toggle() {
        if (this._open)
            this.close();
        else
            this.open();
    }

    _queueResize() {
        Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
            this._resize();
            return GLib.SOURCE_REMOVE;
        });
    }

    _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.x = primary.x + (primary.width - myWidth) / 2;
        this._hiddenY = primary.y + Main.layoutManager.panelBox.height - myHeight;
        this._targetY = this._hiddenY + myHeight;
        this.y = this._hiddenY;
        this.width = myWidth;
        this.height = myHeight;
        this._objInspector.set_size(Math.floor(myWidth * 0.8), Math.floor(myHeight * 0.8));
        this._objInspector.set_position(this.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
    vfunc_key_press_event(keyPressEvent) {
        let symbol = keyPressEvent.keyval;
        if (symbol == Clutter.KEY_Escape) {
            this.close();
            return Clutter.EVENT_STOP;
        }
        // Ctrl+PgUp and Ctrl+PgDown switches tabs in the notebook view
        if (keyPressEvent.modifier_state & Clutter.ModifierType.CONTROL_MASK) {
            if (symbol == Clutter.KEY_Page_Up)
                this._notebook.prevTab();
            else if (symbol == Clutter.KEY_Page_Down)
                this._notebook.nextTab();
        }
        return super.vfunc_key_press_event(keyPressEvent);
    }

    open() {
        if (this._open)
            return;

        let grab = Main.pushModal(this, { actionMode: Shell.ActionMode.LOOKING_GLASS });
        if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
            Main.popModal(grab);
            return;
        }

        this._grab = grab;
        this._notebook.selectIndex(0);
        this.show();
        this._open = true;
        this._history.lastItem();

        this.remove_all_transitions();

        // We inverse compensate for the slow-down so you can change the factor
        // through LookingGlass without long waits.
        let duration = LG_ANIMATION_TIME / St.Settings.get().slow_down_factor;
        this.ease({
            y: this._targetY,
            duration,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        this._windowList.update();
        this._entry.grab_key_focus();
    }

    close() {
        if (!this._open)
            return;

        this._objInspector.hide();

        this._open = false;
        this.remove_all_transitions();

        this.setBorderPaintTarget(null);

        let settings = St.Settings.get();
        let duration = Math.min(LG_ANIMATION_TIME / settings.slow_down_factor,
                                LG_ANIMATION_TIME);
        this.ease({
            y: this._hiddenY,
            duration,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                Main.popModal(this._grab);
                this._grab = null;
                this.hide();
            },
        });
    }

    get isOpen() {
        return this._open;
    }
});
(uuay)ibusManager.js5*// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getIBusManager */

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

const IBusCandidatePopup = imports.ui.ibusCandidatePopup;

Gio._promisify(IBus.Bus.prototype,
    'list_engines_async', 'list_engines_async_finish');
Gio._promisify(IBus.Bus.prototype,
    'request_name_async', 'request_name_async_finish');
Gio._promisify(IBus.Bus.prototype,
    'get_global_engine_async', 'get_global_engine_async_finish');
Gio._promisify(IBus.Bus.prototype,
    'set_global_engine_async', 'set_global_engine_async_finish');
Gio._promisify(Shell, 'util_systemd_unit_exists');

// Ensure runtime version matches
_checkIBusVersion(1, 5, 2);

let _ibusManager = null;
const IBUS_SYSTEMD_SERVICE = 'org.freedesktop.IBus.session.GNOME.service';

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 new Error(`Found IBus version ${
        IBus.MAJOR_VERSION}.${IBus.MINOR_VERSION}.${IBus.MINOR_VERSION} ` +
        `but required is ${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 = new Map();
        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._queueSpawn();
    }

    async _ibusSystemdServiceExists() {
        if (this._ibusIsSystemdService)
            return true;

        try {
            this._ibusIsSystemdService =
                await Shell.util_systemd_unit_exists(
                    IBUS_SYSTEMD_SERVICE, null);
        } catch (e) {
            this._ibusIsSystemdService = false;
        }

        return this._ibusIsSystemdService;
    }

    async _queueSpawn() {
        const isSystemdService = await this._ibusSystemdServiceExists();
        if (!isSystemdService)
            this._spawn(Meta.is_wayland_compositor() ? [] : ['--xim']);
    }

    _tryAppendEnv(env, varname) {
        const value = GLib.getenv(varname);
        if (value)
            env.push(`${varname}=${value}`);
    }

    _spawn(extraArgs = []) {
        try {
            let cmdLine = ['ibus-daemon', '--panel', 'disable', ...extraArgs];
            let env = [];

            this._tryAppendEnv(env, 'DBUS_SESSION_BUS_ADDRESS');
            this._tryAppendEnv(env, 'WAYLAND_DISPLAY');
            this._tryAppendEnv(env, 'HOME');
            this._tryAppendEnv(env, 'LANG');
            this._tryAppendEnv(env, 'LC_CTYPE');
            this._tryAppendEnv(env, 'COMPOSE_FILE');
            this._tryAppendEnv(env, 'DISPLAY');

            // Use DO_NOT_REAP_CHILD to avoid adouble-fork internally
            // since ibus-daemon refuses to start with init as its parent.
            const [success_, pid] = GLib.spawn_async(
                null, cmdLine, env,
                GLib.SpawnFlags.SEARCH_PATH | GLib.SpawnFlags.DO_NOT_REAP_CHILD,
                () => {
                    try {
                        global.context.restore_rlimit_nofile();
                    } catch (err) {
                    }
                }
            );
            GLib.child_watch_add(
                GLib.PRIORITY_DEFAULT,
                pid,
                () => GLib.spawn_close_pid(pid)
            );
        } catch (e) {
            log(`Failed to launch ibus-daemon: ${e.message}`);
        }
    }

    async restartDaemon(extraArgs = []) {
        const isSystemdService = await this._ibusSystemdServiceExists();
        if (!isSystemdService)
            this._spawn(['-r', ...extraArgs]);
    }

    _clear() {
        if (this._cancellable) {
            this._cancellable.cancel();
            this._cancellable = null;
        }

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

        if (this._panelService)
            this._panelService.destroy();

        this._panelService = null;
        this._candidatePopup.setPanelService(null);
        this._engines.clear();
        this._ready = false;
        this._registerPropertiesId = 0;
        this._currentEngineName = null;

        this.emit('ready', false);
    }

    _onConnected() {
        this._cancellable = new Gio.Cancellable();
        this._initEngines();
        this._initPanelService();
    }

    async _initEngines() {
        try {
            const enginesList =
                await this._ibus.list_engines_async(-1, this._cancellable);
            for (let i = 0; i < enginesList.length; ++i) {
                let name = enginesList[i].get_name();
                this._engines.set(name, enginesList[i]);
            }
            this._updateReadiness();
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;

            logError(e);
            this._clear();
        }
    }

    async _initPanelService() {
        try {
            await this._ibus.request_name_async(IBus.SERVICE_PANEL,
                IBus.BusNameFlag.REPLACE_EXISTING, -1, this._cancellable);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                logError(e);
                this._clear();
            }
            return;
        }

        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) {
        }
        this._updateReadiness();

        try {
            // If an engine is already active we need to get its properties
            const engine =
                await this._ibus.get_global_engine_async(-1, this._cancellable);
            this._engineChanged(this._ibus, engine.get_name());
        } catch (e) {
        }
    }

    _updateReadiness() {
        this._ready = this._engines.size > 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.has(id))
            return null;

        return this._engines.get(id);
    }

    async 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;
        }

        try {
            await this._ibus.set_global_engine_async(id,
                this._MAX_INPUT_SOURCE_ACTIVATION_TIME,
                this._cancellable);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        }
        if (callback)
            callback();
    }

    preloadEngines(ids) {
        if (!this._ibus || ids.length == 0)
            return;

        if (this._preloadEnginesId != 0) {
            GLib.source_remove(this._preloadEnginesId);
            this._preloadEnginesId = 0;
        }

        this._preloadEnginesId =
            GLib.timeout_add_seconds(
                GLib.PRIORITY_DEFAULT,
                this._PRELOAD_ENGINES_DELAY_TIME,
                () => {
                    this._ibus.preload_engines_async(
                        ids,
                        -1,
                        this._cancellable,
                        null);
                    this._preloadEnginesId = 0;
                    return GLib.SOURCE_REMOVE;
                });
    }
};
Signals.addSignalMethods(IBusManager.prototype);
(uuay)switcherPopup.js'T// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SwitcherPopup, SwitcherList */

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

const Main = imports.ui.main;

var POPUP_DELAY_TIMEOUT = 150; // milliseconds

var POPUP_SCROLL_TIME = 100; // milliseconds
var POPUP_FADE_OUT_TIME = 100; // milliseconds

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({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
}, class SwitcherPopup extends St.Widget {
    _init(items) {
        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);

        Main.layoutManager.connectObject(
            'system-modal-opened', () => this.destroy(), 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) {
        this.set_allocation(box);

        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 [, childNaturalHeight] = this._switcherList.get_preferred_height(primary.width - hPadding);
        let [, 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);
    }

    _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;

        let grab = Main.pushModal(this);
        // We expect at least a keyboard grab here
        if ((grab.get_seat_state() & Clutter.GrabState.KEYBOARD) === 0) {
            Main.popModal(grab);
            return false;
        }
        this._grab = grab;
        this._haveModal = true;
        this._modifierMask = primaryModifier(mask);

        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 true;
            }
        } else {
            this._resetNoModsTimeout();
        }

        // We delay showing the popup so that fast Alt+Tab users aren't
        // disturbed by the popup briefly flashing.
        this._initialDelayTimeoutId = GLib.timeout_add(
            GLib.PRIORITY_DEFAULT,
            POPUP_DELAY_TIMEOUT,
            () => {
                this._showImmediately();
                return GLib.SOURCE_REMOVE;
            });
        GLib.Source.set_name_by_id(this._initialDelayTimeoutId, '[gnome-shell] Main.osdWindow.cancel');
        return true;
    }

    _showImmediately() {
        if (this._initialDelayTimeoutId === 0)
            return;

        GLib.source_remove(this._initialDelayTimeoutId);
        this._initialDelayTimeoutId = 0;

        Main.osdWindowManager.hideAll();
        this.opacity = 255;
    }

    _next() {
        return mod(this._selectedIndex + 1, this._items.length);
    }

    _previous() {
        return mod(this._selectedIndex - 1, this._items.length);
    }

    _keyPressHandler(_keysym, _action) {
        throw new GObject.NotImplementedError(`_keyPressHandler in ${this.constructor.name}`);
    }

    vfunc_key_press_event(keyEvent) {
        let keysym = keyEvent.keyval;
        let action = global.display.get_keybinding_action(
            keyEvent.hardware_keycode, keyEvent.modifier_state);

        this._disableHover();

        if (this._keyPressHandler(keysym, action) != Clutter.EVENT_PROPAGATE) {
            this._showImmediately();
            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.KEY_Escape || keysym === Clutter.KEY_Tab)
            this.fadeAndDestroy();

        // Allow to explicitly select the current item; this is particularly
        // useful for no-modifier popups
        if (keysym === Clutter.KEY_space ||
            keysym === Clutter.KEY_Return ||
            keysym === Clutter.KEY_KP_Enter ||
            keysym === Clutter.KEY_ISO_Enter)
            this._finish(keyEvent.time);

        return Clutter.EVENT_STOP;
    }

    vfunc_key_release_event(keyEvent) {
        if (this._modifierMask) {
            let [x_, y_, mods] = global.get_pointer();
            let state = mods & this._modifierMask;

            if (state == 0)
                this._finish(keyEvent.time);
        } else {
            this._resetNoModsTimeout();
        }

        return Clutter.EVENT_STOP;
    }

    vfunc_button_press_event() {
        /* We clicked outside */
        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());
    }

    vfunc_scroll_event(scrollEvent) {
        this._disableHover();

        this._scrollHandler(scrollEvent.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;

            if (n < this._selectedIndex)
                newIndex = this._selectedIndex - 1;
            else if (n === this._selectedIndex)
                newIndex = Math.min(n, this._items.length - 1);
            else if (n > this._selectedIndex)
                return; // No need to select something new in this case

            this._select(newIndex);
        } else {
            this.fadeAndDestroy();
        }
    }

    _itemRemoved(switcher, n) {
        this._itemRemovedHandler(n);
    }

    _disableHover() {
        this.mouseActive = false;

        if (this._motionTimeoutId != 0)
            GLib.source_remove(this._motionTimeoutId);

        this._motionTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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)
            GLib.source_remove(this._noModsTimeoutId);

        this._noModsTimeoutId = GLib.timeout_add(
            GLib.PRIORITY_DEFAULT,
            NO_MODS_TIMEOUT,
            () => {
                this._finish(global.display.get_current_time_roundtrip());
                this._noModsTimeoutId = 0;
                return GLib.SOURCE_REMOVE;
            });
    }

    _popModal() {
        if (this._haveModal) {
            Main.popModal(this._grab);
            this._grab = null;
            this._haveModal = false;
        }
    }

    fadeAndDestroy() {
        this._popModal();
        if (this.opacity > 0) {
            this.ease({
                opacity: 0,
                duration: POPUP_FADE_OUT_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => this.destroy(),
            });
        } else {
            this.destroy();
        }
    }

    _finish(_timestamp) {
        this.fadeAndDestroy();
    }

    _onDestroy() {
        this._popModal();

        if (this._motionTimeoutId != 0)
            GLib.source_remove(this._motionTimeoutId);
        if (this._initialDelayTimeoutId != 0)
            GLib.source_remove(this._initialDelayTimeoutId);
        if (this._noModsTimeoutId != 0)
            GLib.source_remove(this._noModsTimeoutId);

        // Make sure the SwitcherList is always destroyed, it may not be
        // a child of the actor at this point.
        if (this._switcherList)
            this._switcherList.destroy();
    }

    _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);

        bbox.connect('clicked', () => this._onItemClicked(bbox));
        bbox.connect('motion-event', () => this._onItemMotion(bbox));

        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);
    }

    addAccessibleState(index, state) {
        this._items[index].add_accessible_state(state);
    }

    removeAccessibleState(index, state) {
        this._items[index].remove_accessible_state(state);
    }

    _onItemClicked(item) {
        this._itemActivated(this._items.indexOf(item));
    }

    _onItemMotion(item) {
        // Avoid reentrancy
        if (item !== this._items[this._highlighted])
            this._itemEntered(this._items.indexOf(item));

        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] = adjustment.get_values();
        let [absItemX] = this._items[index].get_transformed_position();
        let [result_, posX, posY_] = this.transform_stage_point(absItemX, 0);
        let [containerWidth] = this.get_transformed_size();
        if (posX + this._items[index].get_width() > containerWidth)
            this._scrollToRight(index);
        else if (this._items[index].allocation.x1 - value < 0)
            this._scrollToLeft(index);
    }

    _scrollToLeft(index) {
        let adjustment = this._scrollView.hscroll.adjustment;
        let [value, lower_, upper, stepIncrement_, pageIncrement_, pageSize] = adjustment.get_values();

        let item = this._items[index];

        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._scrollableRight = true;
        adjustment.ease(value, {
            progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            duration: POPUP_SCROLL_TIME,
            onComplete: () => {
                if (index === 0)
                    this._scrollableLeft = false;
                this.queue_relayout();
            },
        });
    }

    _scrollToRight(index) {
        let adjustment = this._scrollView.hscroll.adjustment;
        let [value, lower_, upper, stepIncrement_, pageIncrement_, pageSize] = adjustment.get_values();

        let item = this._items[index];

        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;
        adjustment.ease(value, {
            progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            duration: POPUP_SCROLL_TIME,
            onComplete: () => {
                if (index === 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) {
                [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] = 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) {
        this.set_allocation(box);

        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 [minListWidth] = this._list.get_preferred_width(height);

        let childBox = new Clutter.ActorBox();
        let scrollable = minListWidth > width;

        this._scrollView.allocate(contentBox);

        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);
        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);
        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)workspaceThumbnail.jsQ�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WorkspaceThumbnail, ThumbnailsBox */

const { Clutter, Gio, GLib, GObject, Graphene, Meta, Shell, St } = imports.gi;

const DND = imports.ui.dnd;
const Main = imports.ui.main;
const { TransientSignalHolder } = imports.misc.signalTracker;
const Util = imports.misc.util;
const Workspace = imports.ui.workspace;

const NUM_WORKSPACES_THRESHOLD = 2;

// The maximum size of a thumbnail is 5% the width and height of the screen
var MAX_THUMBNAIL_SCALE = 0.05;

var RESCALE_ANIMATION_TIME = 200;
var SLIDE_ANIMATION_TIME = 200;

// 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 = GObject.registerClass({
    Signals: {
        'drag-begin': {},
        'drag-cancelled': {},
        'drag-end': {},
        'selected': { param_types: [GObject.TYPE_UINT] },
    },
}, class WindowClone extends Clutter.Actor {
    _init(realWindow) {
        let clone = new Clutter.Clone({ source: realWindow });
        super._init({
            layout_manager: new PrimaryActorLayout(clone),
            reactive: true,
        });
        this._delegate = this;

        this.add_child(clone);
        this.realWindow = realWindow;
        this.metaWindow = realWindow.meta_window;

        this.realWindow.connectObject(
            'notify::position', this._onPositionChanged.bind(this),
            'destroy', () => {
                // First destroy the clone and then destroy everything
                // This will ensure that we never see it in the _disconnectSignals loop
                clone.destroy();
                this.destroy();
            }, this);
        this._onPositionChanged();

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

        this._draggable = DND.makeDraggable(this, {
            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 parent = this.get_parent();
        let actualAbove = this.getActualStackAbove();
        if (actualAbove == null)
            parent.set_child_below_sibling(this, null);
        else
            parent.set_child_above_sibling(this, actualAbove);
    }

    addAttachedDialog(win) {
        this._doAddAttachedDialog(win, win.get_compositor_private());
    }

    _doAddAttachedDialog(metaDialog, realDialog) {
        let clone = new Clutter.Clone({ source: realDialog });
        this._updateDialogPosition(realDialog, clone);

        realDialog.connectObject(
            'notify::position', dialog => this._updateDialogPosition(dialog, clone),
            'destroy', () => clone.destroy(), this);
        this.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.set_position(this.realWindow.x, this.realWindow.y);
    }

    _onDestroy() {
        this._delegate = null;

        if (this.inDrag) {
            this.emit('drag-end');
            this.inDrag = false;
        }
    }

    vfunc_button_press_event() {
        return Clutter.EVENT_STOP;
    }

    vfunc_button_release_event(buttonEvent) {
        this.emit('selected', buttonEvent.time);

        return Clutter.EVENT_STOP;
    }

    vfunc_touch_event(touchEvent) {
        if (touchEvent.type != Clutter.EventType.TOUCH_END ||
            !global.display.is_pointer_emulating_sequence(touchEvent.sequence))
            return Clutter.EVENT_PROPAGATE;

        this.emit('selected', touchEvent.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.
        let parent = this.get_parent();
        if (parent !== null) {
            if (this._stackAbove == null)
                parent.set_child_below_sibling(this, null);
            else
                parent.set_child_above_sibling(this, this._stackAbove);
        }


        this.emit('drag-end');
    }
});


var ThumbnailState = {
    NEW:            0,
    EXPANDING:      1,
    EXPANDED:       2,
    ANIMATING_IN:   3,
    NORMAL:         4,
    REMOVING:       5,
    ANIMATING_OUT:  6,
    ANIMATED_OUT:   7,
    COLLAPSING:     8,
    DESTROYED:      9,
};

/**
 * @metaWorkspace: a #Meta.Workspace
 */
var WorkspaceThumbnail = GObject.registerClass({
    Properties: {
        'collapse-fraction': GObject.ParamSpec.double(
            'collapse-fraction', 'collapse-fraction', 'collapse-fraction',
            GObject.ParamFlags.READWRITE,
            0, 1, 0),
        'slide-position': GObject.ParamSpec.double(
            'slide-position', 'slide-position', 'slide-position',
            GObject.ParamFlags.READWRITE,
            0, 1, 0),
    },
}, class WorkspaceThumbnail extends St.Widget {
    _init(metaWorkspace, monitorIndex) {
        super._init({
            clip_to_allocation: true,
            style_class: 'workspace-thumbnail',
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
        });
        this._delegate = this;

        this.metaWorkspace = metaWorkspace;
        this.monitorIndex = monitorIndex;

        this._removed = false;

        this._viewport = new Clutter.Actor();
        this.add_child(this._viewport);

        this._contents = new Clutter.Actor();
        this._viewport.add_child(this._contents);

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

        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 = [];
        for (let i = 0; i < windows.length; i++) {
            windows[i].meta_window.connectObject('notify::minimized',
                this._updateMinimized.bind(this), this);
            this._allWindows.push(windows[i].meta_window);

            if (this._isMyWindow(windows[i]) && this._isOverviewWindow(windows[i]))
                this._addWindowClone(windows[i]);
        }

        // Track window changes
        this.metaWorkspace.connectObject(
            'window-added', this._windowAdded.bind(this),
            'window-removed', this._windowRemoved.bind(this), this);
        global.display.connectObject(
            'window-entered-monitor', this._windowEnteredMonitor.bind(this),
            'window-left-monitor', this._windowLeftMonitor.bind(this), this);

        this.state = ThumbnailState.NORMAL;
        this._slidePosition = 0; // Fully slid in
        this._collapseFraction = 0; // Not collapsed
    }

    setPorthole(x, y, width, height) {
        this._viewport.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 = 1; i < this._windows.length; i++) {
            let clone = this._windows[i];
            const previousClone = this._windows[i - 1];
            clone.setStackAbove(previousClone);
        }
    }

    set slidePosition(slidePosition) {
        if (this._slidePosition == slidePosition)
            return;

        const scale = Util.lerp(1, 0.75, slidePosition);
        this.set_scale(scale, scale);
        this.opacity = Util.lerp(255, 0, slidePosition);

        this._slidePosition = slidePosition;
        this.notify('slide-position');
        this.queue_relayout();
    }

    get slidePosition() {
        return this._slidePosition;
    }

    set collapseFraction(collapseFraction) {
        if (this._collapseFraction == collapseFraction)
            return;
        this._collapseFraction = collapseFraction;
        this.notify('collapse-fraction');
        this.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 = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                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.includes(metaWin)) {
            metaWin.connectObject('notify::minimized',
                this._updateMinimized.bind(this), this);
            this._allWindows.push(metaWin);
        }

        // 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.disconnectObject(this);
            this._allWindows.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);
    }

    workspaceRemoved() {
        if (this._removed)
            return;

        this._removed = true;

        this.metaWorkspace.disconnectObject(this);
        global.display.disconnectObject(this);
        this._allWindows.forEach(w => w.disconnectObject(this));
    }

    _onDestroy() {
        this.workspaceRemoved();
        this._windows = [];
    }

    // 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', (o, 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.connect('destroy', () => {
            this._removeWindowClone(clone.metaWindow);
        });
        this._contents.add_actor(clone);

        if (this._windows.length > 0)
            clone.setStackAbove(this._windows[this._windows.length - 1]);

        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
        if (this.metaWorkspace.active)
            Main.overview.hide();
        else
            this.metaWorkspace.activate(time);
    }

    // Draggable target interface used only by ThumbnailsBox
    handleDragOverInternal(source, actor, time) {
        if (source == Main.xdndHandler) {
            this.metaWorkspace.activate(time);
            return DND.DragMotionResult.CONTINUE;
        }

        if (this.state > ThumbnailState.NORMAL)
            return DND.DragMotionResult.CONTINUE;

        if (source.metaWindow &&
            !this._isMyWindow(source.metaWindow.get_compositor_private()))
            return DND.DragMotionResult.MOVE_DROP;
        if (source.app && source.app.can_open_new_window())
            return DND.DragMotionResult.COPY_DROP;
        if (!source.app && source.shellWorkspaceLaunch)
            return DND.DragMotionResult.COPY_DROP;

        return DND.DragMotionResult.CONTINUE;
    }

    acceptDropInternal(source, actor, time) {
        if (this.state > ThumbnailState.NORMAL)
            return false;

        if (source.metaWindow) {
            let win = source.metaWindow.get_compositor_private();
            if (this._isMyWindow(win))
                return false;

            let metaWindow = win.get_meta_window();
            Main.moveWindowToMonitorAndWorkspace(metaWindow,
                this.monitorIndex, this.metaWorkspace.index());
            return true;
        } else if (source.app && source.app.can_open_new_window()) {
            if (source.animateLaunchAtPos)
                source.animateLaunchAtPos(actor.x, actor.y);

            source.app.open_new_window(this.metaWorkspace.index());
            return true;
        } else if (!source.app && source.shellWorkspaceLaunch) {
            // While unused in our own drag sources, shellWorkspaceLaunch allows
            // extensions to define custom actions for their drag sources.
            source.shellWorkspaceLaunch({
                workspace: this.metaWorkspace.index(),
                timestamp: time,
            });
            return true;
        }

        return false;
    }

    setScale(scaleX, scaleY) {
        this._viewport.set_scale(scaleX, scaleY);
    }
});


var ThumbnailsBox = GObject.registerClass({
    Properties: {
        'expand-fraction': GObject.ParamSpec.double(
            'expand-fraction', 'expand-fraction', 'expand-fraction',
            GObject.ParamFlags.READWRITE,
            0, 1, 1),
        'scale': GObject.ParamSpec.double(
            'scale', 'scale', 'scale',
            GObject.ParamFlags.READWRITE,
            0, Infinity, 0),
        'should-show': GObject.ParamSpec.boolean(
            'should-show', 'should-show', 'should-show',
            GObject.ParamFlags.READABLE,
            true),
    },
}, class ThumbnailsBox extends St.Widget {
    _init(scrollAdjustment, monitorIndex) {
        super._init({
            style_class: 'workspace-thumbnails',
            reactive: true,
            x_align: Clutter.ActorAlign.CENTER,
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
        });

        this._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);

        this._monitorIndex = monitorIndex;

        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._expandFraction = 1;
        this._updateStateId = 0;
        this._pendingScaleUpdate = false;
        this._animatingIndicator = false;

        this._shouldShow = true;

        this._stateCounts = {};
        for (let key in ThumbnailState)
            this._stateCounts[ThumbnailState[key]] = 0;

        this._thumbnails = [];

        Main.overview.connectObject(
            'showing', () => this._createThumbnails(),
            'hidden', () => this._destroyThumbnails(),
            'item-drag-begin', () => this._onDragBegin(),
            'item-drag-end', () => this._onDragEnd(),
            'item-drag-cancelled', () => this._onDragCancelled(),
            'window-drag-begin', () => this._onDragBegin(),
            'window-drag-end', () => this._onDragEnd(),
            'window-drag-cancelled', () => this._onDragCancelled(), this);

        this._settings = new Gio.Settings({ schema_id: MUTTER_SCHEMA });
        this._settings.connect('changed::dynamic-workspaces',
            () => this._updateShouldShow());
        this._updateShouldShow();

        Main.layoutManager.connectObject('monitors-changed', () => {
            this._destroyThumbnails();
            if (Main.overview.visible)
                this._createThumbnails();
        }, this);

        // The porthole is the part of the screen we're showing in the thumbnails
        global.display.connectObject('workareas-changed',
            () => this._updatePorthole(), this);
        this._updatePorthole();

        this.connect('notify::visible', () => {
            if (!this.visible)
                this._queueUpdateStates();
        });
        this.connect('destroy', () => this._onDestroy());

        this._scrollAdjustment = scrollAdjustment;
        this._scrollAdjustment.connectObject('notify::value',
            () => this._updateIndicator(), this);
    }

    setMonitorIndex(monitorIndex) {
        this._monitorIndex = monitorIndex;
    }

    _onDestroy() {
        this._destroyThumbnails();
        this._unqueueUpdateStates();

        if (this._settings)
            this._settings.run_dispose();
        this._settings = null;
    }

    _updateShouldShow() {
        const { nWorkspaces } = global.workspace_manager;
        const shouldShow = this._settings.get_boolean('dynamic-workspaces')
            ? nWorkspaces > NUM_WORKSPACES_THRESHOLD
            : nWorkspaces > 1;

        if (this._shouldShow === shouldShow)
            return;

        this._shouldShow = shouldShow;
        this.notify('should-show');
    }

    _updateIndicator() {
        const { value } = this._scrollAdjustment;
        const { workspaceManager } = global;
        const activeIndex = workspaceManager.get_active_workspace_index();

        this._animatingIndicator = value !== activeIndex;

        if (!this._animatingIndicator)
            this._queueUpdateStates();

        this.queue_relayout();
    }

    _activateThumbnailAtPoint(stageX, stageY, time) {
        const [r_, x] = this.transform_stage_point(stageX, stageY);

        const thumbnail = this._thumbnails.find(t => x >= t.x && x <= t.x + t.width);
        if (thumbnail)
            thumbnail.activate(time);
    }

    vfunc_button_release_event(buttonEvent) {
        let { x, y } = buttonEvent;
        this._activateThumbnailAtPoint(x, y, buttonEvent.time);
        return Clutter.EVENT_STOP;
    }

    vfunc_touch_event(touchEvent) {
        if (touchEvent.type == Clutter.EventType.TOUCH_END &&
            global.display.is_pointer_emulating_sequence(touchEvent.sequence)) {
            let { x, y } = touchEvent;
            this._activateThumbnailAtPoint(x, y, touchEvent.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();
    }

    _getPlaceholderTarget(index, spacing, rtl) {
        const workspace = this._thumbnails[index];

        let targetX1;
        let targetX2;

        if (rtl) {
            const baseX = workspace.x + workspace.width;
            targetX1 = baseX - WORKSPACE_CUT_SIZE;
            targetX2 = baseX + spacing + WORKSPACE_CUT_SIZE;
        } else {
            targetX1 = workspace.x - spacing - WORKSPACE_CUT_SIZE;
            targetX2 = workspace.x + WORKSPACE_CUT_SIZE;
        }

        if (index === 0) {
            if (rtl)
                targetX2 -= spacing + WORKSPACE_CUT_SIZE;
            else
                targetX1 += spacing + WORKSPACE_CUT_SIZE;
        }

        if (index === this._dropPlaceholderPos) {
            const placeholderWidth = this._dropPlaceholder.get_width() + spacing;
            if (rtl)
                targetX2 += placeholderWidth;
            else
                targetX1 -= placeholderWidth;
        }

        return [targetX1, targetX2];
    }

    _withinWorkspace(x, index, rtl) {
        const length = this._thumbnails.length;
        const workspace = this._thumbnails[index];

        let workspaceX1 = workspace.x + WORKSPACE_CUT_SIZE;
        let workspaceX2 = workspace.x + workspace.width - WORKSPACE_CUT_SIZE;

        if (index === length - 1) {
            if (rtl)
                workspaceX1 -= WORKSPACE_CUT_SIZE;
            else
                workspaceX2 += WORKSPACE_CUT_SIZE;
        }

        return x > workspaceX1 && x <= workspaceX2;
    }

    // Draggable target interface
    handleDragOver(source, actor, x, y, time) {
        if (!source.metaWindow &&
            (!source.app || !source.app.can_open_new_window()) &&
            (source.app || !source.shellWorkspaceLaunch) &&
            source != Main.xdndHandler)
            return DND.DragMotionResult.CONTINUE;

        const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
        let canCreateWorkspaces = Meta.prefs_get_dynamic_workspaces();
        let spacing = this.get_theme_node().get_length('spacing');

        this._dropWorkspace = -1;
        let placeholderPos = -1;
        let length = this._thumbnails.length;
        for (let i = 0; i < length; i++) {
            const index = rtl ? length - i - 1 : i;

            if (canCreateWorkspaces && source !== Main.xdndHandler) {
                const [targetStart, targetEnd] =
                    this._getPlaceholderTarget(index, spacing, rtl);

                if (x > targetStart && x <= targetEnd) {
                    placeholderPos = index;
                    break;
                }
            }

            if (this._withinWorkspace(x, index, rtl)) {
                this._dropWorkspace = index;
                break;
            }
        }

        if (this._dropPlaceholderPos != placeholderPos) {
            this._dropPlaceholderPos = placeholderPos;
            this.queue_relayout();
        }

        if (this._dropWorkspace != -1)
            return this._thumbnails[this._dropWorkspace].handleDragOverInternal(source, actor, time);
        else if (this._dropPlaceholderPos != -1)
            return source.metaWindow ? 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, actor, time);
        } else if (this._dropPlaceholderPos != -1) {
            if (!source.metaWindow &&
                (!source.app || !source.app.can_open_new_window()) &&
                (source.app || !source.shellWorkspaceLaunch))
                return false;

            let isWindow = !!source.metaWindow;

            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;
                Main.moveWindowToMonitorAndWorkspace(source.metaWindow,
                    thumbMonitor, newWorkspaceIndex, true);
            } else if (source.app && source.app.can_open_new_window()) {
                if (source.animateLaunchAtPos)
                    source.animateLaunchAtPos(actor.x, actor.y);

                source.app.open_new_window(newWorkspaceIndex);
            } else if (!source.app && source.shellWorkspaceLaunch) {
                // While unused in our own drag sources, shellWorkspaceLaunch allows
                // extensions to define custom actions for their drag sources.
                source.shellWorkspaceLaunch({
                    workspace: newWorkspaceIndex,
                    timestamp: time,
                });
            }

            if (source.app || (!source.app && source.shellWorkspaceLaunch)) {
                // 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.slide_position = 1;
            thumbnail.collapse_fraction = 1;

            this._queueUpdateStates();

            return true;
        } else {
            return false;
        }
    }

    _createThumbnails() {
        if (this._thumbnails.length > 0)
            return;

        const { workspaceManager } = global;
        this._transientSignalHolder = new TransientSignalHolder(this);
        workspaceManager.connectObject(
            'notify::n-workspaces', this._workspacesChanged.bind(this),
            'active-workspace-changed', () => this._updateIndicator(),
            'workspaces-reordered', () => {
                this._thumbnails.sort((a, b) => {
                    return a.metaWorkspace.index() - b.metaWorkspace.index();
                });
                this.queue_relayout();
            }, this._transientSignalHolder);
        Main.overview.connectObject('windows-restacked',
            this._syncStacking.bind(this), this._transientSignalHolder);

        this._targetScale = 0;
        this._scale = 0;
        this._pendingScaleUpdate = false;
        this._unqueueUpdateStates();

        this._stateCounts = {};
        for (let key in ThumbnailState)
            this._stateCounts[ThumbnailState[key]] = 0;

        this.addThumbnails(0, workspaceManager.n_workspaces);

        this._updateShouldShow();
    }

    _destroyThumbnails() {
        if (this._thumbnails.length == 0)
            return;

        this._transientSignalHolder.destroy();
        delete this._transientSignalHolder;

        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;

        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._updateShouldShow();
    }

    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, this._monitorIndex);
            thumbnail.setPorthole(this._porthole.x, this._porthole.y,
                                  this._porthole.width, this._porthole.height);
            this._thumbnails.push(thumbnail);
            this.add_actor(thumbnail);

            if (this._shouldShow && start > 0 && this._spliceIndex === -1) {
                // not the initial fill, and not splicing via DND
                thumbnail.state = ThumbnailState.NEW;
                thumbnail.slide_position = 1; // start slid out
                thumbnail.collapse_fraction = 1; // start fully collapsed
                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.set_child_above_sibling(this._indicator, null);

        // 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) {
        if (this._scale == scale)
            return;

        this._scale = scale;
        this.notify('scale');
        this.queue_relayout();
    }

    get scale() {
        return this._scale;
    }

    _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]);
        }
    }

    _updateStates() {
        this._updateStateId = 0;

        // If we are animating the indicator, wait
        if (this._animatingIndicator)
            return;

        // Likewise if we are in the process of hiding
        if (!this._shouldShow && this.visible)
            return;

        // Then slide out any thumbnails that have been destroyed
        this._iterateStateThumbnails(ThumbnailState.REMOVING, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.ANIMATING_OUT);

            thumbnail.ease_property('slide-position', 1, {
                duration: SLIDE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.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,
        // collapse any removed thumbnails and expand added ones
        this._iterateStateThumbnails(ThumbnailState.ANIMATED_OUT, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.COLLAPSING);
            thumbnail.ease_property('collapse-fraction', 1, {
                duration: RESCALE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._stateCounts[thumbnail.state]--;
                    thumbnail.state = ThumbnailState.DESTROYED;

                    let index = this._thumbnails.indexOf(thumbnail);
                    this._thumbnails.splice(index, 1);
                    thumbnail.destroy();

                    this._queueUpdateStates();
                },
            });
        });

        this._iterateStateThumbnails(ThumbnailState.NEW, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.EXPANDING);
            thumbnail.ease_property('collapse-fraction', 0, {
                duration: SLIDE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._setThumbnailState(thumbnail, ThumbnailState.EXPANDED);
                    this._queueUpdateStates();
                },
            });
        });

        if (this._pendingScaleUpdate) {
            this.ease_property('scale', this._targetScale, {
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                duration: RESCALE_ANIMATION_TIME,
                onComplete: () => this._queueUpdateStates(),
            });
            this._pendingScaleUpdate = false;
        }

        // Wait until that's done
        if (this._scale !== this._targetScale ||
            this._stateCounts[ThumbnailState.COLLAPSING] > 0 ||
            this._stateCounts[ThumbnailState.EXPANDING] > 0)
            return;

        // And then slide in any new thumbnails
        this._iterateStateThumbnails(ThumbnailState.EXPANDED, thumbnail => {
            this._setThumbnailState(thumbnail, ThumbnailState.ANIMATING_IN);
            thumbnail.ease_property('slide-position', 0, {
                duration: SLIDE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._setThumbnailState(thumbnail, ThumbnailState.NORMAL);
                },
            });
        });
    }

    _queueUpdateStates() {
        if (this._updateStateId > 0)
            return;

        this._updateStateId = Meta.later_add(
            Meta.LaterType.BEFORE_REDRAW, () => this._updateStates());
    }

    _unqueueUpdateStates() {
        if (this._updateStateId)
            Meta.later_remove(this._updateStateId);
        this._updateStateId = 0;
    }

    vfunc_get_preferred_height(forWidth) {
        let themeNode = this.get_theme_node();

        forWidth = themeNode.adjust_for_width(forWidth);

        let spacing = themeNode.get_length('spacing');
        let nWorkspaces = this._thumbnails.length;
        let totalSpacing = (nWorkspaces - 1) * spacing;

        const avail = forWidth - totalSpacing;

        let scale = (avail / nWorkspaces) / this._porthole.width;
        scale = Math.min(scale, MAX_THUMBNAIL_SCALE);

        const height = Math.round(this._porthole.height * scale);
        return themeNode.adjust_preferred_height(height, height);
    }

    vfunc_get_preferred_width(_forHeight) {
        // Note that for getPreferredHeight/Width 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 themeNode = this.get_theme_node();

        let spacing = themeNode.get_length('spacing');
        let nWorkspaces = this._thumbnails.length;
        let totalSpacing = (nWorkspaces - 1) * spacing;

        const naturalWidth = this._thumbnails.reduce((accumulator, thumbnail, index) => {
            let workspaceSpacing = 0;

            if (index > 0)
                workspaceSpacing += spacing / 2;
            if (index < this._thumbnails.length - 1)
                workspaceSpacing += spacing / 2;

            const progress = 1 - thumbnail.collapse_fraction;
            const width = (this._porthole.width * MAX_THUMBNAIL_SCALE + workspaceSpacing) * progress;
            return accumulator + width;
        }, 0);

        return themeNode.adjust_preferred_width(totalSpacing, naturalWidth);
    }

    _updatePorthole() {
        if (!Main.layoutManager.monitors[this._monitorIndex]) {
            const { x, y, width, height } = global.stage;
            this._porthole = { x, y, width, height };
        } else {
            this._porthole =
                Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
        }

        this.queue_relayout();
    }

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

        let rtl = Clutter.get_default_text_direction() == Clutter.TextDirection.RTL;

        if (this._thumbnails.length == 0) // not visible
            return;

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

        const portholeWidth = this._porthole.width;
        const portholeHeight = this._porthole.height;
        const spacing = themeNode.get_length('spacing');

        const nWorkspaces = this._thumbnails.length;

        // Compute the scale we'll need once everything is updated,
        // unless we are currently transitioning
        if (this._expandFraction === 1) {
            const totalSpacing = (nWorkspaces - 1) * spacing;
            const availableWidth = (box.get_width() - totalSpacing) / nWorkspaces;

            const hScale = availableWidth / portholeWidth;
            const vScale = box.get_height() / portholeHeight;
            const newScale = Math.min(hScale, vScale);

            if (newScale !== this._targetScale) {
                if (this._targetScale > 0) {
                    // We don't ease 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();
            }
        }

        const ratio = portholeWidth / portholeHeight;
        const thumbnailFullHeight = Math.round(portholeHeight * this._scale);
        const thumbnailWidth = Math.round(thumbnailFullHeight * ratio);
        const thumbnailHeight = thumbnailFullHeight * this._expandFraction;
        const roundedVScale = thumbnailHeight / portholeHeight;

        // We always request size for MAX_THUMBNAIL_SCALE, distribute
        // space evently if we use smaller thumbnails
        const extraWidth =
            (MAX_THUMBNAIL_SCALE * portholeWidth - thumbnailWidth) * nWorkspaces;
        box.x1 += Math.round(extraWidth / 2);
        box.x2 -= Math.round(extraWidth / 2);

        let indicatorValue = this._scrollAdjustment.value;
        let indicatorUpperWs = Math.ceil(indicatorValue);
        let indicatorLowerWs = Math.floor(indicatorValue);

        let indicatorLowerX1 = 0;
        let indicatorLowerX2 = 0;
        let indicatorUpperX1 = 0;
        let indicatorUpperX2 = 0;

        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 x = box.x1;

        if (this._dropPlaceholderPos == -1) {
            this._dropPlaceholder.allocate_preferred_size(
                ...this._dropPlaceholder.get_position());

            Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this._dropPlaceholder.hide();
            });
        }

        let childBox = new Clutter.ActorBox();

        for (let i = 0; i < this._thumbnails.length; i++) {
            const thumbnail = this._thumbnails[i];
            if (i > 0)
                x += spacing - Math.round(thumbnail.collapse_fraction * spacing);

            const y1 = box.y1;
            const y2 = y1 + thumbnailHeight;

            if (i === this._dropPlaceholderPos) {
                const [, placeholderWidth] = this._dropPlaceholder.get_preferred_width(-1);
                childBox.y1 = y1;
                childBox.y2 = y2;

                if (rtl) {
                    childBox.x2 = box.x2 - Math.round(x);
                    childBox.x1 = box.x2 - Math.round(x + placeholderWidth);
                } else {
                    childBox.x1 = Math.round(x);
                    childBox.x2 = Math.round(x + placeholderWidth);
                }

                this._dropPlaceholder.allocate(childBox);

                Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                    this._dropPlaceholder.show();
                });
                x += placeholderWidth + spacing;
            }

            // We might end up with thumbnailWidth being something like 99.33
            // pixels. To make this work and not end up with a gap at the end,
            // we need some thumbnails to be 99 pixels and some 100 pixels width;
            // we compute an actual scale separately for each thumbnail.
            const x1 = Math.round(x);
            const x2 = Math.round(x + thumbnailWidth);
            const roundedHScale = (x2 - x1) / portholeWidth;

            // Allocating a scaled actor is funny - x1/y1 correspond to the origin
            // of the actor, but x2/y2 are increased by the *unscaled* size.
            if (rtl) {
                childBox.x2 = box.x2 - x1;
                childBox.x1 = box.x2 - (x1 + thumbnailWidth);
            } else {
                childBox.x1 = x1;
                childBox.x2 = x1 + thumbnailWidth;
            }
            childBox.y1 = y1;
            childBox.y2 = y1 + thumbnailHeight;

            thumbnail.setScale(roundedHScale, roundedVScale);
            thumbnail.allocate(childBox);

            if (i === indicatorUpperWs) {
                indicatorUpperX1 = childBox.x1;
                indicatorUpperX2 = childBox.x2;
            }
            if (i === indicatorLowerWs) {
                indicatorLowerX1 = childBox.x1;
                indicatorLowerX2 = childBox.x2;
            }

            // 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
            x += thumbnailWidth - Math.round(thumbnailWidth * thumbnail.collapse_fraction);
        }

        childBox.y1 = box.y1;
        childBox.y2 = box.y1 + thumbnailHeight;

        const indicatorX1 = indicatorLowerX1 +
            (indicatorUpperX1 - indicatorLowerX1) * (indicatorValue % 1);
        const indicatorX2 = indicatorLowerX2 +
            (indicatorUpperX2 - indicatorLowerX2) * (indicatorValue % 1);

        childBox.x1 = indicatorX1 - indicatorLeftFullBorder;
        childBox.x2 = indicatorX2 + indicatorRightFullBorder;
        childBox.y1 -= indicatorTopFullBorder;
        childBox.y2 += indicatorBottomFullBorder;
        this._indicator.allocate(childBox);
    }

    get shouldShow() {
        return this._shouldShow;
    }

    set expandFraction(expandFraction) {
        if (this._expandFraction === expandFraction)
            return;
        this._expandFraction = expandFraction;
        this.notify('expand-fraction');
        this.queue_relayout();
    }

    get expandFraction() {
        return this._expandFraction;
    }
});
(uuay)scripting.js,0// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported sleep, waitLeisure, createTestWindow, waitTestWindows,
            destroyTestWindows, defineScriptEvent, scriptEvent,
            collectStatistics, runPerfScript */

const { Gio, GLib, Meta, Shell } = imports.gi;

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 an async function that uses await when it wants to let the main
// loop run.
//
//    await Scripting.sleep(1000);
//    main.overview.show();
//    await Scripting.waitLeisure();
//

/**
 * sleep:
 * @param {number} milliseconds - number of milliseconds to wait
 * @returns {Promise} that resolves after @milliseconds ms
 *
 * 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 = GLib.timeout_add(GLib.PRIORITY_DEFAULT, milliseconds, () => {
            resolve();
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(id, '[gnome-shell] sleep');
    });
}

/**
 * waitLeisure:
 * @returns {Promise} that resolves when the shell is idle
 *
 * 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:
 * @param {Object} params: options for window creation.
 *   {number} [params.width=640] - width of window, in pixels
 *   {number} [params.height=480] - height of window, in pixels
 *   {bool} [params.alpha=false] - whether the window should have an alpha channel
 *   {bool} [params.maximized=false] - whether the window should be created maximized
 *   {bool} [params.redraws=false] - whether the window should continually redraw itself
 * @returns {Promise}
 *
 * 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:
 * @returns {Promise}
 *
 * 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:
 * @returns {Promise}
 *
 * 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
 * @param {string} name: The event will be called script.<name>
 * @param {string} 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
 * @param {string} 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, ', ');
            const prefix = i === primary ? '*' : '';
            Shell.write_string_to_stream(out,
                `"${prefix}${monitor.width}x${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) {
    try {
        await scriptModule.run();
    } 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
 * @param {Object} scriptModule: module object with run and finish
 *    functions and event handlers
 * @param {string} outputFile: path to write output to
 *
 * 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)powerProfiles.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Gio, GObject } = 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 = 'net.hadess.PowerProfiles';
const OBJECT_PATH = '/net/hadess/PowerProfiles';

const PowerProfilesIface = loadInterfaceXML('net.hadess.PowerProfiles');
const PowerProfilesProxy = Gio.DBusProxy.makeProxyWrapper(PowerProfilesIface);

const PROFILE_LABELS = {
    'performance': C_('Power profile', 'Performance'),
    'balanced': C_('Power profile', 'Balanced'),
    'power-saver': C_('Power profile', 'Power Saver'),
};
const PROFILE_ICONS = {
    'performance': 'power-profile-performance-symbolic',
    'balanced': 'power-profile-balanced-symbolic',
    'power-saver': 'power-profile-power-saver-symbolic',
};

var Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        this._profileItems = new Map();
        this._updateProfiles = true;

        this._proxy = new PowerProfilesProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
            (proxy, error) => {
                if (error) {
                    log(error.message);
                } else {
                    this._proxy.connect('g-properties-changed',
                        (p, properties) => {
                            const propertyNames = properties.deep_unpack();
                            this._updateProfiles = 'Profiles' in propertyNames;
                            this._sync();
                        });
                }
                this._sync();
            });

        this._item = new PopupMenu.PopupSubMenuMenuItem('', true);

        this._profileSection = new PopupMenu.PopupMenuSection();
        this._item.menu.addMenuItem(this._profileSection);
        this._item.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        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();
        this._sync();
    }

    _sessionUpdated() {
        const sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        this.menu.setSensitive(sensitive);
    }

    _sync() {
        this._item.visible = this._proxy.g_name_owner !== null;

        if (!this._item.visible)
            return;

        if (this._updateProfiles) {
            this._profileSection.removeAll();
            this._profileItems.clear();

            const profiles = this._proxy.Profiles
                .map(p => p.Profile.unpack())
                .reverse();
            for (const profile of profiles) {
                const label = PROFILE_LABELS[profile];
                if (!label)
                    continue;

                const item = new PopupMenu.PopupMenuItem(label);
                item.connect('activate',
                    () => (this._proxy.ActiveProfile = profile));
                this._profileItems.set(profile, item);
                this._profileSection.addMenuItem(item);
            }
            this._updateProfiles = false;
        }

        for (const [profile, item] of this._profileItems) {
            item.setOrnament(profile === this._proxy.ActiveProfile
                ? PopupMenu.Ornament.DOT
                : PopupMenu.Ornament.NONE);
        }

        this._item.label.text = PROFILE_LABELS[this._proxy.ActiveProfile];
        this._item.icon.icon_name = PROFILE_ICONS[this._proxy.ActiveProfile];
    }
});
(uuay)remoteSearch.js.// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported loadRemoteSearchProviders */

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>
<method name="XUbuntuCancel" />
</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>
<method name="XUbuntuCancel" />
</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);
                if (!appInfo.should_show())
                    return;
            } 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 ${path}: ${e}`);
        }
    }

    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');

    const disabled = searchSettings.get_strv('disabled');
    const enabled = searchSettings.get_strv('enabled');

    loadedProviders = loadedProviders.filter(provider => {
        let appId = provider.appInfo.get_id();

        if (provider.defaultEnabled)
            return !disabled.includes(appId);
        else
            return enabled.includes(appId);
    });

    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 gFlags = Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES;
        if (autoStart)
            gFlags |= Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION;
        else
            gFlags |= 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,
            gFlags,
        });
        this.proxy.init_async(GLib.PRIORITY_DEFAULT, 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']) {
            const [
                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, 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 D-Bus search provider ${this.id}: ${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 D-Bus search provider ${this.id} during GetResultMetas: ${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 => 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);
    }

    XUbuntuCancel(cancellable, callback) {
        this.proxy.XUbuntuCancelRemote((results, error) => {
                if (error &&
                    !error.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD) &&
                    !error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                    log('Received error from DBus search provider %s during XUbuntuCancel: %s'.format(this.id, String(error)));
                } else if (callback && !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)polkitAgent.jsz=// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */

const {
    AccountsService, Clutter, GLib, GObject,
    Pango, PolkitAgent, Polkit, Shell, St,
} = imports.gi;

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;
const Util = imports.misc.util;

const DialogMode = {
    AUTH: 0,
    CONFIRM: 1,
};

const DIALOG_ICON_SIZE = 64;

const DELAYED_RESET_TIMEOUT = 200;

var AuthenticationDialog = GObject.registerClass({
    Signals: { 'done': { param_types: [GObject.TYPE_BOOLEAN] } },
}, class AuthenticationDialog extends ModalDialog.ModalDialog {
    _init(actionId, description, cookie, userNames) {
        super._init({ styleClass: 'prompt-dialog' });

        this.actionId = actionId;
        this.message = description;
        this.userNames = userNames;

        Main.sessionMode.connectObject('updated', () => {
            this.visible = !Main.sessionMode.isLocked;
        }, this);

        this.connect('closed', this._onDialogClosed.bind(this));

        let title = _("Authentication Required");

        let headerContent = new Dialog.MessageDialogContent({ title, description });
        this.contentLayout.add_child(headerContent);

        let bodyContent = new Dialog.MessageDialogContent();

        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.includes(userName))
            userName = 'root';
        if (!userNames.includes(userName))
            userName = userNames[0];

        this._user = AccountsService.UserManager.get_default().get_user(userName);

        let userBox = new St.BoxLayout({
            style_class: 'polkit-dialog-user-layout',
            vertical: true,
        });
        bodyContent.add_child(userBox);

        this._userAvatar = new UserWidget.Avatar(this._user, {
            iconSize: DIALOG_ICON_SIZE,
        });
        this._userAvatar.x_align = Clutter.ActorAlign.CENTER;
        userBox.add_child(this._userAvatar);

        this._userLabel = new St.Label({
            style_class: userName === 'root'
                ? 'polkit-dialog-user-root-label'
                : 'polkit-dialog-user-label',
        });

        if (userName === 'root')
            this._userLabel.text = _('Administrator');

        userBox.add_child(this._userLabel);

        let passwordBox = new St.BoxLayout({
            style_class: 'prompt-dialog-password-layout',
            vertical: true,
        });

        this._passwordEntry = new St.PasswordEntry({
            style_class: 'prompt-dialog-password-entry',
            text: "",
            can_focus: true,
            visible: false,
            x_align: Clutter.ActorAlign.CENTER,
        });
        ShellEntry.addContextMenu(this._passwordEntry);
        this._passwordEntry.clutter_text.connect('activate', this._onEntryActivate.bind(this));
        this._passwordEntry.bind_property('reactive',
            this._passwordEntry.clutter_text, 'editable',
            GObject.BindingFlags.SYNC_CREATE);
        passwordBox.add_child(this._passwordEntry);

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

        let capsLockWarning = new ShellEntry.CapsLockWarning();
        this._passwordEntry.bind_property('visible',
            capsLockWarning, 'visible',
            GObject.BindingFlags.SYNC_CREATE);
        warningBox.add_child(capsLockWarning);

        this._errorMessageLabel = new St.Label({
            style_class: 'prompt-dialog-error-label',
            visible: false,
        });
        this._errorMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._errorMessageLabel.clutter_text.line_wrap = true;
        warningBox.add_child(this._errorMessageLabel);

        this._infoMessageLabel = new St.Label({
            style_class: 'prompt-dialog-info-label',
            visible: false,
        });
        this._infoMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._infoMessageLabel.clutter_text.line_wrap = true;
        warningBox.add_child(this._infoMessageLabel);

        /* 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' });
        this._nullMessageLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._nullMessageLabel.clutter_text.line_wrap = true;
        warningBox.add_child(this._nullMessageLabel);

        passwordBox.add_child(warningBox);
        bodyContent.add_child(passwordBox);

        this._cancelButton = this.addButton({
            label: _('Cancel'),
            action: this.cancel.bind(this),
            key: Clutter.KEY_Escape,
        });
        this._okButton = this.addButton({
            label: _('Authenticate'),
            action: this._onAuthenticateButtonPressed.bind(this),
            reactive: false,
        });
        this._okButton.bind_property('reactive',
            this._okButton, 'can-focus',
            GObject.BindingFlags.SYNC_CREATE);

        this._passwordEntry.clutter_text.connect('text-changed', text => {
            this._okButton.reactive = text.get_text().length > 0;
        });

        this.contentLayout.add_child(bodyContent);

        this._doneEmitted = false;

        this._mode = -1;

        this._identityToAuth = Polkit.UnixUser.new_for_name(userName);
        this._cookie = cookie;

        this._user.connectObject(
            'notify::is-loaded', this._onUserChanged.bind(this),
            'changed', this._onUserChanged.bind(this), this);
        this._onUserChanged();
    }

    _initiateSession() {
        this._destroySession(DELAYED_RESET_TIMEOUT);

        this._session = new PolkitAgent.Session({
            identity: this._identityToAuth,
            cookie: this._cookie,
        });
        this._session.connectObject(
            'completed', this._onSessionCompleted.bind(this),
            'request', this._onSessionRequest.bind(this),
            'show-error', this._onSessionShowError.bind(this),
            'show-info', this._onSessionShowInfo.bind(this), 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);
        }
    }

    _onEntryActivate() {
        let response = this._passwordEntry.get_text();
        if (response.length === 0)
            return;

        this._passwordEntry.reactive = false;
        this._okButton.reactive = 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() {
        if (this._mode === DialogMode.CONFIRM)
            this._initiateSession();
        else
            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) {
                /* 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();

                Util.wiggle(this._passwordEntry);
            }

            /* Try and authenticate again */
            this._initiateSession();
        }
    }

    _onSessionRequest(session, request, echoOn) {
        if (this._sessionRequestTimeoutId) {
            GLib.source_remove(this._sessionRequestTimeoutId);
            this._sessionRequestTimeoutId = 0;
        }

        // Hack: The request string comes directly from PAM, if it's "Password:"
        // we replace it with our own to allow localization, if it's something
        // else we remove the last colon and any trailing or leading spaces.
        if (request === 'Password:' || request === 'Password: ')
            this._passwordEntry.hint_text = _('Password');
        else
            this._passwordEntry.hint_text = request.replace(/: *$/, '').trim();

        this._passwordEntry.password_visible = echoOn;

        this._passwordEntry.show();
        this._passwordEntry.set_text('');
        this._passwordEntry.reactive  = true;
        this._okButton.reactive = false;

        this._ensureOpen();
        this._passwordEntry.grab_key_focus();
    }

    _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(delay = 0) {
        this._session?.disconnectObject(this);

        if (!this._completed)
            this._session?.cancel();

        this._completed = false;
        this._session = null;

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

        let resetDialog = () => {
            this._sessionRequestTimeoutId = 0;

            if (this.state != ModalDialog.State.OPENED)
                return GLib.SOURCE_REMOVE;

            this._passwordEntry.hide();
            this._cancelButton.grab_key_focus();
            this._okButton.reactive = false;

            return GLib.SOURCE_REMOVE;
        };

        if (delay) {
            this._sessionRequestTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay, resetDialog);
            GLib.Source.set_name_by_id(this._sessionRequestTimeoutId, '[gnome-shell] this._sessionRequestTimeoutId');
        } else {
            resetDialog();
        }
    }

    _onUserChanged() {
        if (!this._user.is_loaded)
            return;

        let userName = this._user.get_user_name();
        let realName = this._user.get_real_name();

        if (userName !== 'root')
            this._userLabel.set_text(realName);

        this._userAvatar.update();

        if (this._user.get_password_mode() === AccountsService.UserPasswordMode.NONE) {
            if (this._mode === DialogMode.CONFIRM)
                return;

            this._mode = DialogMode.CONFIRM;
            this._destroySession();

            this._okButton.reactive = true;

            /* We normally open the dialog when we get a "request" signal, but
             * since in this case initiating a session would perform the
             * authentication, only open the dialog and initiate the session
             * when the user confirmed. */
            this._ensureOpen();
        } else {
            if (this._mode === DialogMode.AUTH)
                return;

            this._mode = DialogMode.AUTH;
            this._initiateSession();
        }
    }

    close(timestamp) {
        // Ensure cleanup if the dialog was never shown
        if (this.state === ModalDialog.State.CLOSED)
            this._onDialogClosed();
        super.close(timestamp);
    }

    cancel() {
        this.close(global.get_current_time());
        this._emitDone(true);
    }

    _onDialogClosed() {
        Main.sessionMode.disconnectObject(this);

        if (this._sessionRequestTimeoutId)
            GLib.source_remove(this._sessionRequestTimeoutId);
        this._sessionRequestTimeoutId = 0;

        this._user?.disconnectObject(this);
        this._user = null;

        this._destroySession();
    }
});

var AuthenticationAgent = GObject.registerClass(
class AuthenticationAgent extends Shell.PolkitAuthenticationAgent {
    _init() {
        super._init();

        this._currentDialog = null;
        this.connect('initiate', this._onInitiate.bind(this));
        this.connect('cancel', this._onCancel.bind(this));
        this._sessionUpdatedId = 0;
    }

    enable() {
        try {
            this.register();
        } catch (e) {
            log('Failed to register AuthenticationAgent');
        }
    }

    disable() {
        try {
            this.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) {
            Main.sessionMode.connectObject('updated', () => {
                Main.sessionMode.disconnectObject(this);

                this._onInitiate(nativeAgent, actionId, message, iconName, cookie, userNames);
            }, this);
            return;
        }

        this._currentDialog = new AuthenticationDialog(actionId, message, cookie, userNames);
        this._currentDialog.connect('done', this._onDialogDone.bind(this));
    }

    _onCancel(_nativeAgent) {
        this._completeRequest(false);
    }

    _onDialogDone(_dialog, dismissed) {
        this._completeRequest(dismissed);
    }

    _completeRequest(dismissed) {
        this._currentDialog.close();
        this._currentDialog = null;

        Main.sessionMode.disconnectObject(this);

        this.complete(dismissed);
    }
});

var Component = AuthenticationAgent;
(uuay)edgeDragAction.jsb// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported EdgeDragAction */

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': {},
        'progress': { param_types: [GObject.TYPE_DOUBLE] },
    },
}, class EdgeDragAction extends Clutter.GestureAction {
    _init(side, allowedModes) {
        super._init();
        this._side = side;
        this._allowedModes = allowedModes;
        this.set_n_touch_points(1);
        this.set_threshold_trigger_edge(Clutter.GestureTriggerEdge.AFTER);

        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;
        }

        if (this._side === St.Side.TOP ||
            this._side === St.Side.BOTTOM)
            this.emit('progress', offsetY);
        else
            this.emit('progress', offsetX);

        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');
        else
            this.cancel();
    }
});
(uuay)screenShield.js�c// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

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

const GnomeSession = imports.misc.gnomeSession;
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 { adjustAnimationTime } = imports.ui.environment;

const SCREENSAVER_SCHEMA = 'org.gnome.desktop.screensaver';
const LOCK_ENABLED_KEY = 'lock-enabled';
const LOCK_DELAY_KEY = 'lock-delay';
const SUSPEND_LOCK_ENABLED_KEY = 'ubuntu-lock-on-suspend';

const LOCKDOWN_SCHEMA = 'org.gnome.desktop.lockdown';
const DISABLE_LOCK_KEY = 'disable-lock-screen';

const LOCKED_STATE_STR = 'screenShield.locked';

const LOGIN_SCREEN_SCHEMA = 'com.ubuntu.login-screen';
const LOGIN_SCREEN_BACKGROUND_COLOR_KEY = 'background-color';
const LOGIN_SCREEN_BACKGROUND_PICTURE_URI_KEY = 'background-picture-uri';
const LOGIN_SCREEN_BACKGROUND_REPEAT_KEY = 'background-repeat';
const LOGIN_SCREEN_BACKGROUND_SIZE_KEY = 'background-size';

// 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
// - CURTAIN_SLIDE_TIME is used when raising the shield before unlocking
var STANDARD_FADE_TIME = 10000;
var MANUAL_FADE_TIME = 300;
var CURTAIN_SLIDE_TIME = 300;

/**
 * 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._lockDialogGroup = new St.Widget({
            x_expand: true,
            y_expand: true,
            reactive: true,
            can_focus: true,
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
            name: 'lockDialogGroup',
        });

        this.actor.add_actor(this._lockScreenGroup);
        this.actor.add_actor(this._lockDialogGroup);

        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._activateDialog();
                                       });

        this._oVirtCredentialsManager = OVirt.getOVirtCredentialsManager();
        this._oVirtCredentialsManager.connect('user-authenticated',
                                              () => {
                                                  if (this._isLocked)
                                                      this._activateDialog();
                                              });

        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._settings.connect(`changed::${SUSPEND_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._loginScreenSettings = new Gio.Settings({ schema_id: LOGIN_SCREEN_SCHEMA });
        [
            LOGIN_SCREEN_BACKGROUND_COLOR_KEY,
            LOGIN_SCREEN_BACKGROUND_PICTURE_URI_KEY,
            LOGIN_SCREEN_BACKGROUND_REPEAT_KEY,
            LOGIN_SCREEN_BACKGROUND_SIZE_KEY,
        ].forEach(schema => this._loginScreenSettings.connect(`changed::${schema}`,
            () => this._refreshBackground()));
        this._refreshBackground();

        this._isModal = false;
        this._isGreeter = false;
        this._isActive = false;
        this._isLocked = false;
        this._inUnlockAnimation = false;
        this._inhibited = 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('notify::active', this._onLongLightbox.bind(this));
        this._shortLightbox = new Lightbox.Lightbox(Main.uiGroup, {
            inhibitEvents: true,
            fadeFactor: 1,
        });
        this._shortLightbox.connect('notify::active', this._onShortLightbox.bind(this));

        this.idleMonitor = global.backend.get_core_idle_monitor();
        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');

        this._syncInhibitor();
    }

    _setLocked(locked) {
        let prevIsLocked = this._isLocked;
        this._isLocked = locked;

        if (prevIsLocked !== this._isLocked)
            this.emit('locked-changed');

        if (this._loginSession)
            this._loginSession.SetLockedHintRemote(locked);
    }

    _activateDialog() {
        if (this._isLocked) {
            this._ensureUnlockDialog(true /* allowCancel */);
            this._dialog.activate();
        } 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._dialog.grab_key_focus();
        }
    }

    _becomeModal() {
        if (this._isModal)
            return true;

        let grab = Main.pushModal(Main.uiGroup, { actionMode: Shell.ActionMode.LOCK_SCREEN });

        // We expect at least a keyboard grab here
        this._isModal = (grab.get_seat_state() & Clutter.GrabState.KEYBOARD) !== 0;
        if (this._isModal)
            this._grab = grab;
        else
            Main.popModal(grab);

        return this._isModal;
    }

    _refreshBackground() {
        const inlineStyle = [];

        const getSetting = s => this._loginScreenSettings.get_string(s);
        const backgroundColor = getSetting(LOGIN_SCREEN_BACKGROUND_COLOR_KEY);
        const backgroundPictureUri = getSetting(LOGIN_SCREEN_BACKGROUND_PICTURE_URI_KEY);
        const backgroundRepeat = getSetting(LOGIN_SCREEN_BACKGROUND_REPEAT_KEY);
        const backgroundSize = getSetting(LOGIN_SCREEN_BACKGROUND_SIZE_KEY);

        if (backgroundColor)
            inlineStyle.push(`background-color: ${backgroundColor}`);
        if (backgroundPictureUri)
            inlineStyle.push(`background-image: url("${backgroundPictureUri}")`);
        if (backgroundRepeat !== 'default')
            inlineStyle.push(`background-repeat: ${backgroundRepeat}`);
        if (backgroundSize !== 'default')
            inlineStyle.push(`background-size: ${backgroundSize}`);

        this._lockDialogGroup.set_style(inlineStyle.join('; '));
    }

    async _syncInhibitor() {
        const lockEnabled = this._settings.get_boolean(LOCK_ENABLED_KEY) ||
                            this._settings.get_boolean(SUSPEND_LOCK_ENABLED_KEY);
        const lockLocked = this._lockSettings.get_boolean(DISABLE_LOCK_KEY);
        const inhibit = !!this._loginSession && this._loginSession.Active &&
                         !this._isActive && lockEnabled && !lockLocked &&
                         !!Main.sessionMode.unlockDialog;

        if (inhibit === this._inhibited)
            return;

        this._inhibited = inhibit;

        this._inhibitCancellable?.cancel();
        this._inhibitCancellable = new Gio.Cancellable();

        if (inhibit) {
            try {
                this._inhibitor = await this._loginManager.inhibit(
                    _('GNOME needs to lock the screen'),
                    this._inhibitCancellable);
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                    log('Failed to inhibit suspend: %s'.format(e.message));
            }
        } else {
            this._inhibitor?.close(null);
            this._inhibitor = null;
        }
    }

    _prepareForSleep(loginManager, aboutToSuspend) {
        if (aboutToSuspend) {
            if (this._settings.get_boolean(SUSPEND_LOCK_ENABLED_KEY))
                this.lock(true);
        } else {
            this._wakeUpScreen();
        }
    }

    _onStatusChanged(status) {
        if (status != GnomeSession.PresenceStatus.IDLE)
            return;

        this._maybeCancelDialog();

        if (this._longLightbox.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 them 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(
                adjustAnimationTime(STANDARD_FADE_TIME),
                this._settings.get_uint(LOCK_DELAY_KEY) * 1000);
            this._lockTimeoutId = GLib.timeout_add(
                GLib.PRIORITY_DEFAULT,
                lockTimeout,
                () => {
                    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, null);
        lightbox.lightOn(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; we just go back to the lock screen curtain
        // - 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._isLocked) {
            this._longLightbox.lightOff();
            this._shortLightbox.lightOff();
        } else {
            this.deactivate(false);
        }
    }

    _onLongLightbox(lightBox) {
        if (lightBox.active)
            this.activate(false);
    }

    _onShortLightbox(lightBox) {
        if (lightBox.active)
            this._completeLockScreenShown();
    }

    showDialog() {
        if (!this._becomeModal()) {
            // In the login screen, this is a hard error. Fail-whale
            const error = new GLib.Error(
                Gio.IOErrorEnum, Gio.IOErrorEnum.FAILED,
                'Could not acquire modal grab for the login screen. Aborting login process.');
            global.context.terminate_with_error(error);
        }

        this.actor.show();
        this._isGreeter = Main.sessionMode.isGreeter;
        this._isLocked = true;
        this._ensureUnlockDialog(true);
    }

    _hideLockScreenComplete() {
        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);
        }
    }

    _showPointer() {
        this._cursorTracker.set_pointer_visible(true);

        if (this._motionId) {
            global.stage.disconnect(this._motionId);
            this._motionId = 0;
        }
    }

    _hidePointerUntilMotion() {
        this._motionId = global.stage.connect('captured-event', (stage, event) => {
            if (event.type() === Clutter.EventType.MOTION)
                this._showPointer();

            return Clutter.EVENT_PROPAGATE;
        });
        this._cursorTracker.set_pointer_visible(false);
    }

    _hideLockScreen(animate) {
        if (this._lockScreenState == MessageTray.State.HIDDEN)
            return;

        this._lockScreenState = MessageTray.State.HIDING;

        this._lockDialogGroup.remove_all_transitions();

        if (animate) {
            // Animate 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._lockDialogGroup.translation_y;
            let velocity = global.stage.height / CURTAIN_SLIDE_TIME;
            let duration = delta / velocity;

            this._lockDialogGroup.ease({
                translation_y: -h,
                duration,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => this._hideLockScreenComplete(),
            });
        } else {
            this._hideLockScreenComplete();
        }

        this._showPointer();
    }

    _ensureUnlockDialog(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)) {
                // 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._wakeUpScreenId = this._dialog.connect(
                'wake-up-screen', this._wakeUpScreen.bind(this));
        }

        this._dialog.allowCancel = allowCancel;
        this._dialog.grab_key_focus();
        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._lockScreenGroup.show();
        this._lockScreenState = MessageTray.State.SHOWING;

        let fadeToBlack = params.fadeToBlack;

        if (params.animateLockScreen) {
            this._lockDialogGroup.translation_y = -global.screen_height;
            this._lockDialogGroup.remove_all_transitions();
            this._lockDialogGroup.ease({
                translation_y: 0,
                duration: Overview.ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._lockScreenShown({ fadeToBlack, animateFade: true });
                },
            });
        } else {
            this._lockDialogGroup.translation_y = 0;
            this._lockScreenShown({ fadeToBlack, animateFade: false });
        }

        this._dialog.grab_key_focus();
    }

    _lockScreenShown(params) {
        this._hidePointerUntilMotion();

        this._lockScreenState = MessageTray.State.SHOWN;

        if (params.fadeToBlack && params.animateFade) {
            // Take a beat

            let id = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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');
    }

    _wakeUpScreen() {
        if (!this.active)
            return; // already woken up, or not yet asleep
        this._onUserBecameActive();
        this.emit('wake-up-screen');
    }

    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);

        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._grab);
            this._grab = null;
            this._isModal = false;
        }

        this._longLightbox.lightOff();
        this._shortLightbox.lightOff();

        this._lockDialogGroup.ease({
            translation_y: -global.screen_height,
            duration: Overview.ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this._completeDeactivate(),
        });
    }

    _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) {
            GLib.source_remove(this._lockTimeoutId);
            this._lockTimeoutId = 0;
        }

        this._activationTime = 0;
        this._setActive(false);
        this._setLocked(false);
        global.set_runtime_state(LOCKED_STATE_STR, null);
    }

    activate(animate) {
        if (this._activationTime == 0)
            this._activationTime = GLib.get_monotonic_time();

        if (!this._ensureUnlockDialog(true))
            return;

        this.actor.show();

        if (Main.sessionMode.currentMode !== 'unlock-dialog') {
            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());

        this.activate(animate);

        const lock = this._isGreeter
            ? true
            : user.password_mode !== AccountsService.UserPasswordMode.NONE;
        this._setLocked(lock);
    }

    // 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);
            return GLib.SOURCE_REMOVE;
        });
    }
};
Signals.addSignalMethods(ScreenShield.prototype);
(uuay)swipeTracker.jsAg// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SwipeTracker */

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

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

// FIXME: ideally these values matches physical touchpad size. We can get the
// correct values for gnome-shell specifically, since mutter uses libinput
// directly, but GTK apps cannot get it, so use an arbitrary value so that
// it's consistent with apps.
const TOUCHPAD_BASE_HEIGHT = 300;
const TOUCHPAD_BASE_WIDTH = 400;

const EVENT_HISTORY_THRESHOLD_MS = 150;

const SCROLL_MULTIPLIER = 10;

const MIN_ANIMATION_DURATION = 100;
const MAX_ANIMATION_DURATION = 400;
const VELOCITY_THRESHOLD_TOUCH = 0.3;
const VELOCITY_THRESHOLD_TOUCHPAD = 0.6;
const DECELERATION_TOUCH = 0.998;
const DECELERATION_TOUCHPAD = 0.997;
const VELOCITY_CURVE_THRESHOLD = 2;
const DECELERATION_PARABOLA_MULTIPLIER = 0.35;
const DRAG_THRESHOLD_DISTANCE = 16;

// Derivative of easeOutCubic at t=0
const DURATION_MULTIPLIER = 3;
const ANIMATION_BASE_VELOCITY = 0.002;
const EPSILON = 0.005;

const GESTURE_FINGER_COUNT = 3;

const State = {
    NONE: 0,
    SCROLLING: 1,
};

const TouchpadState = {
    NONE: 0,
    PENDING: 1,
    HANDLING: 2,
    IGNORED: 3,
};

const EventHistory = class {
    constructor() {
        this.reset();
    }

    reset() {
        this._data = [];
    }

    trim(time) {
        const thresholdTime = time - EVENT_HISTORY_THRESHOLD_MS;
        const index = this._data.findIndex(r => r.time >= thresholdTime);

        this._data.splice(0, index);
    }

    append(time, delta) {
        this.trim(time);

        this._data.push({ time, delta });
    }

    calculateVelocity() {
        if (this._data.length < 2)
            return 0;

        const firstTime = this._data[0].time;
        const lastTime = this._data[this._data.length - 1].time;

        if (firstTime === lastTime)
            return 0;

        const totalDelta = this._data.slice(1).map(a => a.delta).reduce((a, b) => a + b);
        const period = lastTime - firstTime;

        return totalDelta / period;
    }
};

const TouchpadSwipeGesture = GObject.registerClass({
    Properties: {
        'enabled': GObject.ParamSpec.boolean(
            'enabled', 'enabled', 'enabled',
            GObject.ParamFlags.READWRITE,
            true),
        'orientation': GObject.ParamSpec.enum(
            'orientation', 'orientation', 'orientation',
            GObject.ParamFlags.READWRITE,
            Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
    },
    Signals: {
        'begin':  { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
        'update': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
        'end':    { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
    },
}, class TouchpadSwipeGesture extends GObject.Object {
    _init(allowedModes) {
        super._init();
        this._allowedModes = allowedModes;
        this._state = TouchpadState.NONE;
        this._cumulativeX = 0;
        this._cumulativeY = 0;
        this._touchpadSettings = new Gio.Settings({
            schema_id: 'org.gnome.desktop.peripherals.touchpad',
        });

        global.stage.connectObject(
            'captured-event::touchpad', this._handleEvent.bind(this), this);
    }

    _handleEvent(actor, event) {
        if (event.type() !== Clutter.EventType.TOUCHPAD_SWIPE)
            return Clutter.EVENT_PROPAGATE;

        if (event.get_gesture_phase() === Clutter.TouchpadGesturePhase.BEGIN)
            this._state = TouchpadState.NONE;

        if (event.get_touchpad_gesture_finger_count() !== GESTURE_FINGER_COUNT)
            return Clutter.EVENT_PROPAGATE;

        if ((this._allowedModes & Main.actionMode) === 0)
            return Clutter.EVENT_PROPAGATE;

        if (!this.enabled)
            return Clutter.EVENT_PROPAGATE;

        if (this._state === TouchpadState.IGNORED)
            return Clutter.EVENT_PROPAGATE;

        let time = event.get_time();

        const [x, y] = event.get_coords();
        const [dx, dy] = event.get_gesture_motion_delta_unaccelerated();

        if (this._state === TouchpadState.NONE) {
            if (dx === 0 && dy === 0)
                return Clutter.EVENT_PROPAGATE;

            this._cumulativeX = 0;
            this._cumulativeY = 0;
            this._state = TouchpadState.PENDING;
        }

        if (this._state === TouchpadState.PENDING) {
            this._cumulativeX += dx;
            this._cumulativeY += dy;

            const cdx = this._cumulativeX;
            const cdy = this._cumulativeY;
            const distance = Math.sqrt(cdx * cdx + cdy * cdy);

            if (distance >= DRAG_THRESHOLD_DISTANCE) {
                const gestureOrientation = Math.abs(cdx) > Math.abs(cdy)
                    ? Clutter.Orientation.HORIZONTAL
                    : Clutter.Orientation.VERTICAL;

                this._cumulativeX = 0;
                this._cumulativeY = 0;

                if (gestureOrientation === this.orientation) {
                    this._state = TouchpadState.HANDLING;
                    this.emit('begin', time, x, y);
                } else {
                    this._state = TouchpadState.IGNORED;
                    return Clutter.EVENT_PROPAGATE;
                }
            } else {
                return Clutter.EVENT_PROPAGATE;
            }
        }

        const vertical = this.orientation === Clutter.Orientation.VERTICAL;
        let delta = vertical ? dy : dx;
        const distance = vertical ? TOUCHPAD_BASE_HEIGHT : TOUCHPAD_BASE_WIDTH;

        switch (event.get_gesture_phase()) {
        case Clutter.TouchpadGesturePhase.BEGIN:
        case Clutter.TouchpadGesturePhase.UPDATE:
            if (this._touchpadSettings.get_boolean('natural-scroll'))
                delta = -delta;

            this.emit('update', time, delta, distance);
            break;

        case Clutter.TouchpadGesturePhase.END:
        case Clutter.TouchpadGesturePhase.CANCEL:
            this.emit('end', time, distance);
            this._state = TouchpadState.NONE;
            break;
        }

        return this._state === TouchpadState.HANDLING
            ? Clutter.EVENT_STOP
            : Clutter.EVENT_PROPAGATE;
    }

    destroy() {
        global.stage.disconnectObject(this);
    }
});

const TouchSwipeGesture = GObject.registerClass({
    Properties: {
        'distance': GObject.ParamSpec.double(
            'distance', 'distance', 'distance',
            GObject.ParamFlags.READWRITE,
            0, Infinity, 0),
        'orientation': GObject.ParamSpec.enum(
            'orientation', 'orientation', 'orientation',
            GObject.ParamFlags.READWRITE,
            Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
    },
    Signals: {
        'begin':  { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
        'update': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
        'end':    { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
        'cancel': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
    },
}, class TouchSwipeGesture extends Clutter.GestureAction {
    _init(allowedModes, nTouchPoints, thresholdTriggerEdge) {
        super._init();
        this.set_n_touch_points(nTouchPoints);
        this.set_threshold_trigger_edge(thresholdTriggerEdge);

        this._allowedModes = allowedModes;
        this._distance = global.screen_height;

        global.display.connect('grab-op-begin', () => {
            this.cancel();
        });

        this._lastPosition = 0;
    }

    get distance() {
        return this._distance;
    }

    set distance(distance) {
        if (this._distance === distance)
            return;

        this._distance = distance;
        this.notify('distance');
    }

    vfunc_gesture_prepare(actor) {
        if (!super.vfunc_gesture_prepare(actor))
            return false;

        if ((this._allowedModes & Main.actionMode) === 0)
            return false;

        let time = this.get_last_event(0).get_time();
        let [xPress, yPress] = this.get_press_coords(0);
        let [x, y] = this.get_motion_coords(0);
        const [xDelta, yDelta] = [x - xPress, y - yPress];
        const swipeOrientation = Math.abs(xDelta) > Math.abs(yDelta)
            ? Clutter.Orientation.HORIZONTAL : Clutter.Orientation.VERTICAL;

        if (swipeOrientation !== this.orientation)
            return false;

        this._lastPosition =
            this.orientation === Clutter.Orientation.VERTICAL ? y : x;

        this.emit('begin', time, xPress, yPress);
        return true;
    }

    vfunc_gesture_progress(_actor) {
        let [x, y] = this.get_motion_coords(0);
        let pos = this.orientation === Clutter.Orientation.VERTICAL ? y : x;

        let delta = pos - this._lastPosition;
        this._lastPosition = pos;

        let time = this.get_last_event(0).get_time();

        this.emit('update', time, -delta, this._distance);

        return true;
    }

    vfunc_gesture_end(_actor) {
        let time = this.get_last_event(0).get_time();

        this.emit('end', time, this._distance);
    }

    vfunc_gesture_cancel(_actor) {
        let time = Clutter.get_current_event_time();

        this.emit('cancel', time, this._distance);
    }
});

const ScrollGesture = GObject.registerClass({
    Properties: {
        'enabled': GObject.ParamSpec.boolean(
            'enabled', 'enabled', 'enabled',
            GObject.ParamFlags.READWRITE,
            true),
        'orientation': GObject.ParamSpec.enum(
            'orientation', 'orientation', 'orientation',
            GObject.ParamFlags.READWRITE,
            Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
        'scroll-modifiers': GObject.ParamSpec.flags(
            'scroll-modifiers', 'scroll-modifiers', 'scroll-modifiers',
            GObject.ParamFlags.READWRITE,
            Clutter.ModifierType, 0),
    },
    Signals: {
        'begin':  { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
        'update': { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE, GObject.TYPE_DOUBLE] },
        'end':    { param_types: [GObject.TYPE_UINT, GObject.TYPE_DOUBLE] },
    },
}, class ScrollGesture extends GObject.Object {
    _init(actor, allowedModes) {
        super._init();
        this._allowedModes = allowedModes;
        this._began = false;
        this._enabled = true;

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

    get enabled() {
        return this._enabled;
    }

    set enabled(enabled) {
        if (this._enabled === enabled)
            return;

        this._enabled = enabled;
        this._began = false;

        this.notify('enabled');
    }

    canHandleEvent(event) {
        if (event.type() !== Clutter.EventType.SCROLL)
            return false;

        if (event.get_scroll_source() !== Clutter.ScrollSource.FINGER &&
            event.get_source_device().get_device_type() !== Clutter.InputDeviceType.TOUCHPAD_DEVICE)
            return false;

        if (!this.enabled)
            return false;

        if ((this._allowedModes & Main.actionMode) === 0)
            return false;

        if (!this._began && this.scrollModifiers !== 0 &&
            (event.get_state() & this.scrollModifiers) === 0)
            return false;

        return true;
    }

    _handleEvent(actor, event) {
        if (!this.canHandleEvent(event))
            return Clutter.EVENT_PROPAGATE;

        if (event.get_scroll_direction() !== Clutter.ScrollDirection.SMOOTH)
            return Clutter.EVENT_PROPAGATE;

        const vertical = this.orientation === Clutter.Orientation.VERTICAL;
        const distance = vertical ? TOUCHPAD_BASE_HEIGHT : TOUCHPAD_BASE_WIDTH;

        let time = event.get_time();
        let [dx, dy] = event.get_scroll_delta();
        if (dx === 0 && dy === 0) {
            this.emit('end', time, distance);
            this._began = false;
            return Clutter.EVENT_STOP;
        }

        if (!this._began) {
            let [x, y] = event.get_coords();
            this.emit('begin', time, x, y);
            this._began = true;
        }

        const delta = (vertical ? dy : dx) * SCROLL_MULTIPLIER;

        this.emit('update', time, delta, distance);

        return Clutter.EVENT_STOP;
    }
});

// USAGE:
//
// To correctly implement the gesture, there must be handlers for the following
// signals:
//
// begin(tracker, monitor)
//   The handler should check whether a deceleration animation is currently
//   running. If it is, it should stop the animation (without resetting
//   progress). Then it should call:
//   tracker.confirmSwipe(distance, snapPoints, currentProgress, cancelProgress)
//   If it's not called, the swipe would be ignored.
//   The parameters are:
//    * distance: the page size;
//    * snapPoints: an (sorted with ascending order) array of snap points;
//    * currentProgress: the current progress;
//    * cancelprogress: a non-transient value that would be used if the gesture
//      is cancelled.
//   If no animation was running, currentProgress and cancelProgress should be
//   same. The handler may set 'orientation' property here.
//
// update(tracker, progress)
//   The handler should set the progress to the given value.
//
// end(tracker, duration, endProgress)
//   The handler should animate the progress to endProgress. If endProgress is
//   0, it should do nothing after the animation, otherwise it should change the
//   state, e.g. change the current page or switch workspace.
//   NOTE: duration can be 0 in some cases, in this case it should finish
//   instantly.

/** A class for handling swipe gestures */
var SwipeTracker = GObject.registerClass({
    Properties: {
        'enabled': GObject.ParamSpec.boolean(
            'enabled', 'enabled', 'enabled',
            GObject.ParamFlags.READWRITE,
            true),
        'orientation': GObject.ParamSpec.enum(
            'orientation', 'orientation', 'orientation',
            GObject.ParamFlags.READWRITE,
            Clutter.Orientation, Clutter.Orientation.HORIZONTAL),
        'distance': GObject.ParamSpec.double(
            'distance', 'distance', 'distance',
            GObject.ParamFlags.READWRITE,
            0, Infinity, 0),
        'allow-long-swipes': GObject.ParamSpec.boolean(
            'allow-long-swipes', 'allow-long-swipes', 'allow-long-swipes',
            GObject.ParamFlags.READWRITE,
            false),
        'scroll-modifiers': GObject.ParamSpec.flags(
            'scroll-modifiers', 'scroll-modifiers', 'scroll-modifiers',
            GObject.ParamFlags.READWRITE,
            Clutter.ModifierType, 0),
    },
    Signals: {
        'begin':  { param_types: [GObject.TYPE_UINT] },
        'update': { param_types: [GObject.TYPE_DOUBLE] },
        'end':    { param_types: [GObject.TYPE_UINT64, GObject.TYPE_DOUBLE] },
    },
}, class SwipeTracker extends GObject.Object {
    _init(actor, orientation, allowedModes, params) {
        super._init();
        params = Params.parse(params, { allowDrag: true, allowScroll: true });

        this.orientation = orientation;
        this._allowedModes = allowedModes;
        this._enabled = true;
        this._distance = global.screen_height;
        this._history = new EventHistory();
        this._reset();

        this._touchpadGesture = new TouchpadSwipeGesture(allowedModes);
        this._touchpadGesture.connect('begin', this._beginGesture.bind(this));
        this._touchpadGesture.connect('update', this._updateGesture.bind(this));
        this._touchpadGesture.connect('end', this._endTouchpadGesture.bind(this));
        this.bind_property('enabled', this._touchpadGesture, 'enabled', 0);
        this.bind_property('orientation', this._touchpadGesture, 'orientation',
            GObject.BindingFlags.SYNC_CREATE);

        this._touchGesture = new TouchSwipeGesture(allowedModes,
            GESTURE_FINGER_COUNT,
            Clutter.GestureTriggerEdge.AFTER);
        this._touchGesture.connect('begin', this._beginTouchSwipe.bind(this));
        this._touchGesture.connect('update', this._updateGesture.bind(this));
        this._touchGesture.connect('end', this._endTouchGesture.bind(this));
        this._touchGesture.connect('cancel', this._cancelTouchGesture.bind(this));
        this.bind_property('enabled', this._touchGesture, 'enabled', 0);
        this.bind_property('orientation', this._touchGesture, 'orientation',
            GObject.BindingFlags.SYNC_CREATE);
        this.bind_property('distance', this._touchGesture, 'distance', 0);
        global.stage.add_action_full('swipe', Clutter.EventPhase.CAPTURE, this._touchGesture);

        if (params.allowDrag) {
            this._dragGesture = new TouchSwipeGesture(allowedModes, 1,
                Clutter.GestureTriggerEdge.AFTER);
            this._dragGesture.connect('begin', this._beginGesture.bind(this));
            this._dragGesture.connect('update', this._updateGesture.bind(this));
            this._dragGesture.connect('end', this._endTouchGesture.bind(this));
            this._dragGesture.connect('cancel', this._cancelTouchGesture.bind(this));
            this.bind_property('enabled', this._dragGesture, 'enabled', 0);
            this.bind_property('orientation', this._dragGesture, 'orientation',
                GObject.BindingFlags.SYNC_CREATE);
            this.bind_property('distance', this._dragGesture, 'distance', 0);
            actor.add_action(this._dragGesture);
        } else {
            this._dragGesture = null;
        }

        if (params.allowScroll) {
            this._scrollGesture = new ScrollGesture(actor, allowedModes);
            this._scrollGesture.connect('begin', this._beginGesture.bind(this));
            this._scrollGesture.connect('update', this._updateGesture.bind(this));
            this._scrollGesture.connect('end', this._endTouchpadGesture.bind(this));
            this.bind_property('enabled', this._scrollGesture, 'enabled', 0);
            this.bind_property('orientation', this._scrollGesture, 'orientation',
                GObject.BindingFlags.SYNC_CREATE);
            this.bind_property('scroll-modifiers',
                this._scrollGesture, 'scroll-modifiers', 0);
        } else {
            this._scrollGesture = null;
        }
    }

    /**
     * canHandleScrollEvent:
     * @param {Clutter.Event} scrollEvent: an event to check
     * @returns {bool} whether the event can be handled by the tracker
     *
     * This function can be used to combine swipe gesture and mouse
     * scrolling.
     */
    canHandleScrollEvent(scrollEvent) {
        if (!this.enabled || this._scrollGesture === null)
            return false;

        return this._scrollGesture.canHandleEvent(scrollEvent);
    }

    get enabled() {
        return this._enabled;
    }

    set enabled(enabled) {
        if (this._enabled === enabled)
            return;

        this._enabled = enabled;
        if (!enabled && this._state === State.SCROLLING)
            this._interrupt();
        this.notify('enabled');
    }

    get distance() {
        return this._distance;
    }

    set distance(distance) {
        if (this._distance === distance)
            return;

        this._distance = distance;
        this.notify('distance');
    }

    _reset() {
        this._state = State.NONE;

        this._snapPoints = [];
        this._initialProgress = 0;
        this._cancelProgress = 0;

        this._prevOffset = 0;
        this._progress = 0;

        this._cancelled = false;

        this._history.reset();
    }

    _interrupt() {
        this.emit('end', 0, this._cancelProgress);
        this._reset();
    }

    _beginTouchSwipe(gesture, time, x, y) {
        if (this._dragGesture)
            this._dragGesture.cancel();

        this._beginGesture(gesture, time, x, y);
    }

    _beginGesture(gesture, time, x, y) {
        if (this._state === State.SCROLLING)
            return;

        this._history.append(time, 0);

        let rect = new Meta.Rectangle({ x, y, width: 1, height: 1 });
        let monitor = global.display.get_monitor_index_for_rect(rect);

        this.emit('begin', monitor);
    }

    _findClosestPoint(pos) {
        const distances = this._snapPoints.map(x => Math.abs(x - pos));
        const min = Math.min(...distances);
        return distances.indexOf(min);
    }

    _findNextPoint(pos) {
        return this._snapPoints.findIndex(p => p >= pos);
    }

    _findPreviousPoint(pos) {
        const reversedIndex = this._snapPoints.slice().reverse().findIndex(p => p <= pos);
        return this._snapPoints.length - 1 - reversedIndex;
    }

    _findPointForProjection(pos, velocity) {
        const initial = this._findClosestPoint(this._initialProgress);
        const prev = this._findPreviousPoint(pos);
        const next = this._findNextPoint(pos);

        if ((velocity > 0 ? prev : next) === initial)
            return velocity > 0 ? next : prev;

        return this._findClosestPoint(pos);
    }

    _getBounds(pos) {
        if (this.allowLongSwipes)
            return [this._snapPoints[0], this._snapPoints[this._snapPoints.length - 1]];

        const closest = this._findClosestPoint(pos);

        let prev, next;
        if (Math.abs(this._snapPoints[closest] - pos) < EPSILON) {
            prev = next = closest;
        } else {
            prev = this._findPreviousPoint(pos);
            next = this._findNextPoint(pos);
        }

        const lowerIndex = Math.max(prev - 1, 0);
        const upperIndex = Math.min(next + 1, this._snapPoints.length - 1);

        return [this._snapPoints[lowerIndex], this._snapPoints[upperIndex]];
    }

    _updateGesture(gesture, time, delta, distance) {
        if (this._state !== State.SCROLLING)
            return;

        if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
            this._interrupt();
            return;
        }

        if (this.orientation === Clutter.Orientation.HORIZONTAL &&
            Clutter.get_default_text_direction() === Clutter.TextDirection.RTL)
            delta = -delta;

        this._progress += delta / distance;
        this._history.append(time, delta);

        this._progress = Math.clamp(this._progress, ...this._getBounds(this._initialProgress));

        this.emit('update', this._progress);
    }

    _getEndProgress(velocity, distance, isTouchpad) {
        if (this._cancelled)
            return this._cancelProgress;

        const threshold = isTouchpad ? VELOCITY_THRESHOLD_TOUCHPAD : VELOCITY_THRESHOLD_TOUCH;

        if (Math.abs(velocity) < threshold)
            return this._snapPoints[this._findClosestPoint(this._progress)];

        const decel = isTouchpad ? DECELERATION_TOUCHPAD : DECELERATION_TOUCH;
        const slope = decel / (1.0 - decel) / 1000.0;

        let pos;
        if (Math.abs(velocity) > VELOCITY_CURVE_THRESHOLD) {
            const c = slope / 2 / DECELERATION_PARABOLA_MULTIPLIER;
            const x = Math.abs(velocity) - VELOCITY_CURVE_THRESHOLD + c;

            pos = slope * VELOCITY_CURVE_THRESHOLD +
                DECELERATION_PARABOLA_MULTIPLIER * x * x -
                DECELERATION_PARABOLA_MULTIPLIER * c * c;
        } else {
            pos = Math.abs(velocity) * slope;
        }

        pos = pos * Math.sign(velocity) + this._progress;
        pos = Math.clamp(pos, ...this._getBounds(this._initialProgress));

        const index = this._findPointForProjection(pos, velocity);

        return this._snapPoints[index];
    }

    _endTouchGesture(_gesture, time, distance) {
        this._endGesture(time, distance, false);
    }

    _endTouchpadGesture(_gesture, time, distance) {
        this._endGesture(time, distance, true);
    }

    _endGesture(time, distance, isTouchpad) {
        if (this._state !== State.SCROLLING)
            return;

        if ((this._allowedModes & Main.actionMode) === 0 || !this.enabled) {
            this._interrupt();
            return;
        }

        this._history.trim(time);

        let velocity = this._history.calculateVelocity();
        const endProgress = this._getEndProgress(velocity, distance, isTouchpad);

        velocity /= distance;

        if ((endProgress - this._progress) * velocity <= 0)
            velocity = ANIMATION_BASE_VELOCITY;

        const nPoints = Math.max(1, Math.ceil(Math.abs(this._progress - endProgress)));
        const maxDuration = MAX_ANIMATION_DURATION * Math.log2(1 + nPoints);

        let duration = Math.abs((this._progress - endProgress) / velocity * DURATION_MULTIPLIER);
        if (duration > 0)
            duration = Math.clamp(duration, MIN_ANIMATION_DURATION, maxDuration);

        this._reset();
        this.emit('end', duration, endProgress);
    }

    _cancelTouchGesture(_gesture, time, distance) {
        if (this._state !== State.SCROLLING)
            return;

        this._cancelled = true;
        this._endGesture(time, distance, false);
    }

    /**
     * confirmSwipe:
     * @param {number} distance: swipe distance in pixels
     * @param {number[]} snapPoints:
     *     An array of snap points, sorted in ascending order
     * @param {number} currentProgress: initial progress value
     * @param {number} cancelProgress: the value to be used on cancelling
     *
     * Confirms a swipe. User has to call this in 'begin' signal handler,
     * otherwise the swipe wouldn't start. If there's an animation running,
     * it should be stopped first.
     *
     * @cancel_progress must always be a snap point, or a value matching
     * some other non-transient state.
     */
    confirmSwipe(distance, snapPoints, currentProgress, cancelProgress) {
        this.distance = distance;
        this._snapPoints = snapPoints;
        this._initialProgress = currentProgress;
        this._progress = currentProgress;
        this._cancelProgress = cancelProgress;

        this._state = State.SCROLLING;
    }

    destroy() {
        if (this._touchpadGesture) {
            this._touchpadGesture.destroy();
            delete this._touchpadGesture;
        }

        if (this._touchGesture) {
            global.stage.remove_action(this._touchGesture);
            delete this._touchGesture;
        }
    }
});
(uuay)windowManager.js�"// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WindowManager */

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

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 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 IBusManager = imports.misc.ibusManager;
const WorkspaceAnimation = imports.ui.workspaceAnimation;

const { loadInterfaceXML } = imports.misc.fileUtils;

var SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';
var MINIMIZE_WINDOW_ANIMATION_TIME = 400;
var MINIMIZE_WINDOW_ANIMATION_MODE = Clutter.AnimationMode.EASE_OUT_EXPO;
var SHOW_WINDOW_ANIMATION_TIME = 150;
var DIALOG_SHOW_WINDOW_ANIMATION_TIME = 100;
var DESTROY_WINDOW_ANIMATION_TIME = 150;
var DIALOG_DESTROY_WINDOW_ANIMATION_TIME = 100;
var WINDOW_ANIMATION_TIME = 250;
var SCROLL_TIMEOUT_TIME = 150;
var DIM_BRIGHTNESS = -0.3;
var DIM_TIME = 500;
var UNDIM_TIME = 250;
var APP_MOTION_THRESHOLD = 30;

var ONE_SECOND = 1000; // in ms

var MIN_NUM_WORKSPACES = 2;

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);

const WINDOW_DIMMER_EFFECT_NAME = "gnome-shell-window-dimmer";

Gio._promisify(Shell, 'util_start_systemd_unit');
Gio._promisify(Shell, 'util_stop_systemd_unit');

var DisplayChangeDialog = GObject.registerClass(
class DisplayChangeDialog extends ModalDialog.ModalDialog {
    _init(wm) {
        super._init();

        this._wm = wm;

        this._countDown = Meta.MonitorManager.get_display_configuration_timeout();

        // Translators: This string should be shorter than 30 characters
        let title = _('Keep these display settings?');
        let description = this._formatCountDown();

        this._content = new Dialog.MessageDialogContent({ title, description });
        this.contentLayout.add_child(this._content);

        /* Translators: this and the following message should be limited in length,
           to avoid ellipsizing the labels.
        */
        this._cancelButton = this.addButton({
            label: _('Revert Settings'),
            action: this._onFailure.bind(this),
            key: Clutter.KEY_Escape,
        });
        this._okButton = this.addButton({
            label: _('Keep Changes'),
            action: this._onSuccess.bind(this),
            default: true,
        });

        this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, ONE_SECOND, this._tick.bind(this));
        GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] this._tick');
    }

    close(timestamp) {
        if (this._timeoutId > 0) {
            GLib.source_remove(this._timeoutId);
            this._timeoutId = 0;
        }

        super.close(timestamp);
    }

    _formatCountDown() {
        const fmt = ngettext(
            'Settings changes will revert in %d second',
            'Settings changes will revert in %d seconds',
            this._countDown);
        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.description = 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 = GObject.registerClass(
class WindowDimmer extends Clutter.BrightnessContrastEffect {
    _init() {
        super._init({
            name: WINDOW_DIMMER_EFFECT_NAME,
            enabled: false,
        });
    }

    _syncEnabled(dimmed) {
        let animating = this.actor.get_transition(`@effects.${this.name}.brightness`) !== null;

        this.enabled = Meta.prefs_get_attach_modal_dialogs() && (animating || dimmed);
    }

    setDimmed(dimmed, animate) {
        let val = 127 * (1 + (dimmed ? 1 : 0) * DIM_BRIGHTNESS);
        let color = Clutter.Color.new(val, val, val, 255);

        this.actor.ease_property(`@effects.${this.name}.brightness`, color, {
            mode: Clutter.AnimationMode.LINEAR,
            duration: (dimmed ? DIM_TIME : UNDIM_TIME) * (animate ? 1 : 0),
            onStopped: () => this._syncEnabled(dimmed),
        });

        this._syncEnabled(dimmed);
    }
});

function getWindowDimmer(actor) {
    let effect = actor.get_effect(WINDOW_DIMMER_EFFECT_NAME);

    if (!effect) {
        effect = new WindowDimmer();
        actor.add_effect(effect);
    }
    return effect;
}

/*
 * 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));
        workspaceManager.connect('workspaces-reordered', () => {
            this._workspaces.sort((a, b) => a.index() - b.index());
        });
        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));

        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);
        }

        // Enforce minimum number of workspaces
        while (emptyWorkspaces.length < MIN_NUM_WORKSPACES) {
            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 (workspaceManager.n_workspaces === MIN_NUM_WORKSPACES)
                break;
            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)
            GLib.source_remove(workspace._keepAliveId);

        workspace._keepAliveId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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 = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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();
    }

    _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;

        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++) {
                this._workspaces[w].connectObject(
                    'window-added', this._queueCheckWorkspaces.bind(this),
                    'window-removed', this._windowRemoved.bind(this), 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.disconnectObject(this));
        }

        this._queueCheckWorkspaces();

        return false;
    }
};

var TilePreview = GObject.registerClass(
class TilePreview extends St.Widget {
    _init() {
        super._init();
        global.window_group.add_actor(this);

        this._reset();
        this._showing = false;
    }

    open(window, tileRect, monitorIndex) {
        let windowActor = window.get_compositor_private();
        if (!windowActor)
            return;

        global.window_group.set_child_below_sibling(this, 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) {
            const 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.set_size(rect.width, rect.height);
            this.set_position(rect.x, rect.y);
            this.opacity = 0;
        }

        this._showing = true;
        this.show();
        this.ease({
            x: tileRect.x,
            y: tileRect.y,
            width: tileRect.width,
            height: tileRect.height,
            opacity: 255,
            duration: WINDOW_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    close() {
        if (!this._showing)
            return;

        this._showing = false;
        this.ease({
            opacity: 0,
            duration: WINDOW_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this._reset(),
        });
    }

    _reset() {
        this.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.style_class = styles.join(' ');
    }
});

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) {
        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) > APP_MOTION_THRESHOLD ||
                    Math.abs(y - startY) > APP_MOTION_THRESHOLD)
                    return false;
            }
        }

        return true;
    }
});

var ResizePopup = GObject.registerClass(
class ResizePopup extends St.Widget {
    _init() {
        super._init({ 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.add_child(this._label);
        Main.uiGroup.add_actor(this);
    }

    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.set_position(rect.x, rect.y);
        this.set_size(rect.width, rect.height);
    }
});

var WindowManager = class {
    constructor() {
        this._shellwm =  global.window_manager;

        this._minimizing = new Set();
        this._unminimizing = new Set();
        this._mapping = new Set();
        this._resizing = new Set();
        this._resizePending = new Set();
        this._destroying = new Set();

        this._dimmedWindows = [];

        this._skippedActors = new Set();

        this._allowedKeybindings = {};

        this._isWorkspacePrepended = false;
        this._canScroll = true; // limiting scrolling speed

        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));

        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('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.stage.connect('scroll-event', (stage, event) => {
            const allowedModes = Shell.ActionMode.NORMAL;
            if ((allowedModes & Main.actionMode) === 0)
                return Clutter.EVENT_PROPAGATE;

            if (this._workspaceAnimation.canHandleScrollEvent(event))
                return Clutter.EVENT_PROPAGATE;

            if ((event.get_state() & global.display.compositor_modifiers) === 0)
                return Clutter.EVENT_PROPAGATE;

            return this.handleWorkspaceScroll(event);
        });

        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);
                                                });

        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 ?? '');
            }

            if (this._gsdWacomProxy)
                this._gsdWacomProxy.SetOLEDLabelsRemote(pad.get_device_node(), labels);
        });

        global.display.connect('init-xserver', (display, task) => {
            IBusManager.getIBusManager().restartDaemon(['--xim']);

            this._startX11Services(task);

            return true;
        });
        global.display.connect('x11-display-closing', () => {
            if (!Meta.is_wayland_compositor())
                return;

            this._stopX11Services(null);

            IBusManager.getIBusManager().restartDaemon();
        });

        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);

        let appSwitchAction = new AppSwitchAction();
        appSwitchAction.connect('activated', this._switchApp.bind(this));
        global.stage.add_action_full('app-switch', Clutter.EventPhase.CAPTURE, appSwitchAction);

        let mode = Shell.ActionMode.NORMAL;
        let topDragAction = new EdgeDragAction.EdgeDragAction(St.Side.TOP, mode);
        topDragAction.connect('activated',  () => {
            let currentWindow = global.display.focus_window;
            if (currentWindow)
                currentWindow.unmake_fullscreen();
        });

        let updateUnfullscreenGesture = () => {
            let currentWindow = global.display.focus_window;
            topDragAction.enabled = currentWindow && currentWindow.is_fullscreen();
        };

        global.display.connect('notify::focus-window', updateUnfullscreenGesture);
        global.display.connect('in-fullscreen-changed', updateUnfullscreenGesture);
        updateUnfullscreenGesture();

        global.stage.add_action_full('unfullscreen', Clutter.EventPhase.CAPTURE, topDragAction);

        this._workspaceAnimation =
            new WorkspaceAnimation.WorkspaceAnimationController();

        this._shellwm.connect('kill-switch-workspace', () => {
            this._workspaceAnimation.cancelSwitchAnimation();
            this._switchWorkspaceDone();
        });
    }

    async _startX11Services(task) {
        let status = true;
        try {
            await Shell.util_start_systemd_unit(
                'gnome-session-x11-services-ready.target', 'fail', null);
        } catch (e) {
            // Ignore NOT_SUPPORTED error, which indicates we are not systemd
            // managed and gnome-session will have taken care of everything
            // already.
            // Note that we do log cancellation from here.
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_SUPPORTED)) {
                log(`Error starting X11 services: ${e.message}`);
                status = false;
            }
        } finally {
            task.return_boolean(status);
        }
    }

    async _stopX11Services(cancellable) {
        try {
            await Shell.util_stop_systemd_unit(
                'gnome-session-x11-services.target', 'fail', cancellable);
        } catch (e) {
            // Ignore NOT_SUPPORTED error, which indicates we are not systemd
            // managed and gnome-session will have taken care of everything
            // already.
            // Note that we do log cancellation from here.
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_SUPPORTED))
                log(`Error stopping X11 services: ${e.message}`);
        }
    }

    _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;
    }

    _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.add(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() {
        const overviewOpen = Main.overview.visible && !Main.overview.closing;
        return !(overviewOpen || this._workspaceAnimation.gestureActive);
    }

    _shouldAnimateActor(actor, types) {
        if (this._skippedActors.delete(actor))
            return false;

        if (!this._shouldAnimate())
            return false;

        if (!actor.get_texture())
            return false;

        let type = actor.meta_window.get_window_type();
        return types.includes(type);
    }

    _minimizeWindow(shellwm, actor) {
        const 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.add(actor);

        if (actor.meta_window.is_monitor_sized()) {
            actor.ease({
                opacity: 0,
                duration: MINIMIZE_WINDOW_ANIMATION_TIME,
                mode: MINIMIZE_WINDOW_ANIMATION_MODE,
                onStopped: () => this._minimizeWindowDone(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;
            }

            actor.ease({
                scale_x: xScale,
                scale_y: yScale,
                x: xDest,
                y: yDest,
                duration: MINIMIZE_WINDOW_ANIMATION_TIME,
                mode: MINIMIZE_WINDOW_ANIMATION_MODE,
                onStopped: () => this._minimizeWindowDone(shellwm, actor),
            });
        }
    }

    _minimizeWindowDone(shellwm, actor) {
        if (this._minimizing.delete(actor)) {
            actor.remove_all_transitions();
            actor.set_scale(1.0, 1.0);
            actor.set_opacity(255);
            actor.set_pivot_point(0, 0);

            shellwm.completed_minimize(actor);
        }
    }

    _unminimizeWindow(shellwm, actor) {
        const types = [
            Meta.WindowType.NORMAL,
            Meta.WindowType.MODAL_DIALOG,
            Meta.WindowType.DIALOG,
        ];
        if (!this._shouldAnimateActor(actor, types)) {
            shellwm.completed_unminimize(actor);
            return;
        }

        this._unminimizing.add(actor);

        if (actor.meta_window.is_monitor_sized()) {
            actor.opacity = 0;
            actor.set_scale(1.0, 1.0);
            actor.ease({
                opacity: 255,
                duration: MINIMIZE_WINDOW_ANIMATION_TIME,
                mode: MINIMIZE_WINDOW_ANIMATION_MODE,
                onStopped: () => this._unminimizeWindowDone(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_buffer_rect();
            let [xDest, yDest] = [rect.x, rect.y];

            actor.show();
            actor.ease({
                scale_x: 1,
                scale_y: 1,
                x: xDest,
                y: yDest,
                duration: MINIMIZE_WINDOW_ANIMATION_TIME,
                mode: MINIMIZE_WINDOW_ANIMATION_MODE,
                onStopped: () => this._unminimizeWindowDone(shellwm, actor),
            });
        }
    }

    _unminimizeWindowDone(shellwm, actor) {
        if (this._unminimizing.delete(actor)) {
            actor.remove_all_transitions();
            actor.set_scale(1.0, 1.0);
            actor.set_opacity(255);
            actor.set_pivot_point(0, 0);

            shellwm.completed_unminimize(actor);
        }
    }

    _sizeChangeWindow(shellwm, actor, whichChange, oldFrameRect, _oldBufferRect) {
        const types = [Meta.WindowType.NORMAL];
        const shouldAnimate =
            this._shouldAnimateActor(actor, types) &&
            oldFrameRect.width > 0 &&
            oldFrameRect.height > 0;

        if (shouldAnimate)
            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 = actor.paint_to_content(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);

        actor.freeze();

        if (this._clearAnimationInfo(actor)) {
            log(`Old animationInfo removed from actor ${actor}`);
            this._shellwm.completed_size_change(actor);
        }

        actor.connectObject('destroy',
            () => this._clearAnimationInfo(actor), actorClone);

        this._resizePending.add(actor);
        actor.__animationInfo = {
            clone: actorClone,
            oldRect: oldFrameRect,
            frozen: true,
        };
    }

    _sizeChangedWindow(shellwm, actor) {
        if (!actor.__animationInfo)
            return;
        if (this._resizing.has(actor))
            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._resizePending.delete(actor);
        this._resizing.add(actor);

        Main.uiGroup.add_child(actorClone);

        // Now scale and fade out the clone
        actorClone.ease({
            x: targetRect.x,
            y: targetRect.y,
            scale_x: scaleX,
            scale_y: scaleY,
            opacity: 0,
            duration: WINDOW_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        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
        actor.ease({
            scale_x: 1,
            scale_y: 1,
            translation_x: 0,
            translation_y: 0,
            duration: WINDOW_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => this._sizeChangeWindowDone(shellwm, actor),
        });

        // ease didn't animate and cleared the info, we are done
        if (!actor.__animationInfo)
            return;

        // 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.
        actor.thaw();
        actor.__animationInfo.frozen = false;
    }

    _clearAnimationInfo(actor) {
        if (actor.__animationInfo) {
            actor.__animationInfo.clone.destroy();
            if (actor.__animationInfo.frozen)
                actor.thaw();

            delete actor.__animationInfo;
            return true;
        }
        return false;
    }

    _sizeChangeWindowDone(shellwm, actor) {
        if (this._resizing.delete(actor)) {
            actor.remove_all_transitions();
            actor.scale_x = 1.0;
            actor.scale_y = 1.0;
            actor.translation_x = 0;
            actor.translation_y = 0;
            this._clearAnimationInfo(actor);
            this._shellwm.completed_size_change(actor);
        }

        if (this._resizePending.delete(actor)) {
            this._clearAnimationInfo(actor);
            this._shellwm.completed_size_change(actor);
        }
    }

    _checkDimming(window) {
        const shouldDim = window.has_attached_dialogs();

        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;
        dimmer.setDimmed(true, this._shouldAnimate());
    }

    _undimWindow(window) {
        let actor = window.get_compositor_private();
        if (!actor)
            return;
        let dimmer = getWindowDimmer(actor);
        if (!dimmer)
            return;
        dimmer.setDimmed(false, this._shouldAnimate());
    }

    _waitForOverviewToHide() {
        if (!Main.overview.visible)
            return Promise.resolve();

        return new Promise(resolve => {
            const id = Main.overview.connect('hidden', () => {
                Main.overview.disconnect(id);
                resolve();
            });
        });
    }

    async _mapWindow(shellwm, actor) {
        actor._windowType = actor.meta_window.get_window_type();
        actor.meta_window.connectObject('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);
        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());

        const 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.add(actor);

            await this._waitForOverviewToHide();
            actor.ease({
                opacity: 255,
                scale_x: 1,
                scale_y: 1,
                duration: SHOW_WINDOW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_EXPO,
                onStopped: () => this._mapWindowDone(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.add(actor);

            await this._waitForOverviewToHide();
            actor.ease({
                opacity: 255,
                scale_x: 1,
                scale_y: 1,
                duration: DIALOG_SHOW_WINDOW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onStopped: () => this._mapWindowDone(shellwm, actor),
            });
            break;
        default:
            shellwm.completed_map(actor);
        }
    }

    _mapWindowDone(shellwm, actor) {
        if (this._mapping.delete(actor)) {
            actor.remove_all_transitions();
            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);
        }
    }

    _destroyWindow(shellwm, actor) {
        let window = actor.meta_window;
        window.disconnectObject(actor);
        if (window._dimmed) {
            this._dimmedWindows =
                this._dimmedWindows.filter(win => win != window);
        }

        if (window.is_attached_dialog())
            this._checkDimming(window.get_transient_for());

        const 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.add(actor);

            actor.ease({
                opacity: 0,
                scale_x: 0.8,
                scale_y: 0.8,
                duration: DESTROY_WINDOW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onStopped: () => this._destroyWindowDone(shellwm, actor),
            });
            break;
        case Meta.WindowType.MODAL_DIALOG:
        case Meta.WindowType.DIALOG:
            actor.set_pivot_point(0.5, 0.5);
            this._destroying.add(actor);

            if (window.is_attached_dialog()) {
                let parent = window.get_transient_for();
                parent.connectObject('unmanaged', () => {
                    actor.remove_all_transitions();
                    this._destroyWindowDone(shellwm, actor);
                }, actor);
            }

            actor.ease({
                scale_y: 0,
                duration: DIALOG_DESTROY_WINDOW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onStopped: () => this._destroyWindowDone(shellwm, actor),
            });
            break;
        default:
            shellwm.completed_destroy(actor);
        }
    }

    _destroyWindowDone(shellwm, actor) {
        if (this._destroying.delete(actor)) {
            const parent = actor.get_meta_window()?.get_transient_for();
            parent?.disconnectObject(actor);
            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);
    }

    _switchWorkspace(shellwm, from, to, direction) {
        if (!Main.sessionMode.hasWorkspaces || !this._shouldAnimate()) {
            shellwm.completed_switch_workspace();
            return;
        }

        this._switchInProgress = true;

        this._workspaceAnimation.animateSwitch(from, to, direction, () => {
            this._shellwm.completed_switch_workspace();
            this._switchInProgress = false;
        });
    }

    _switchWorkspaceDone() {
        if (!this._switchInProgress)
            return;

        this._shellwm.completed_switch_workspace();
        this._switchInProgress = false;
    }

    _showTilePreview(shellwm, window, tileRect, monitorIndex) {
        if (!this._tilePreview)
            this._tilePreview = new TilePreview();
        this._tilePreview.open(window, tileRect, monitorIndex);
    }

    _hideTilePreview() {
        if (!this._tilePreview)
            return;
        this._tilePreview.close();
    }

    _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());
    }

    _allowFavoriteShortcuts() {
        return Main.sessionMode.hasOverview;
    }

    _switchToApplication(display, window, binding) {
        if (!this._allowFavoriteShortcuts())
            return;

        let [, , , target] = binding.get_name().split('-');
        let apps = AppFavorites.getAppFavorites().getFavorites();
        let app = apps[target - 1];
        if (app) {
            Main.overview.hide();
            app.activate();
        }
    }

    _toggleAppMenu() {
        Main.panel.toggleAppMenu();
    }

    _toggleCalendar() {
        Main.panel.toggleCalendar();
    }

    _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
            let prependTarget;
            if (vertical)
                prependTarget = 'up';
            else if (rtl)
                prependTarget = 'right';
            else
                prependTarget = 'left';
            if (workspaceManager.get_active_workspace_index() === 0 &&
                action === 'move' && target === prependTarget &&
                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) // eslint-disable-line no-lonely-if
                    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(newWs.index());
        }
    }

    actionMoveWorkspace(workspace) {
        if (!Main.sessionMode.hasWorkspaces)
            return;

        if (!workspace.active)
            workspace.activate(global.get_current_time());
    }

    actionMoveWindow(window, workspace) {
        if (!Main.sessionMode.hasWorkspaces)
            return;

        if (!workspace.active) {
            // This won't have any effect for "always sticky" windows
            // (like desktop windows or docks)

            this._workspaceAnimation.movingWindow = window;
            window.change_workspace(workspace);

            global.display.clear_mouse_mode();
            workspace.activate_with_focus(window, global.get_current_time());
        }
    }

    handleWorkspaceScroll(event) {
        if (!this._canScroll)
            return Clutter.EVENT_PROPAGATE;

        if (event.type() !== Clutter.EventType.SCROLL)
            return Clutter.EVENT_PROPAGATE;

        const direction = event.get_scroll_direction();
        if (direction === Clutter.ScrollDirection.SMOOTH)
            return Clutter.EVENT_PROPAGATE;

        const workspaceManager = global.workspace_manager;
        const vertical = workspaceManager.layout_rows === -1;
        const rtl = Clutter.get_default_text_direction() === Clutter.TextDirection.RTL;
        const activeWs = workspaceManager.get_active_workspace();
        let ws;
        switch (direction) {
        case Clutter.ScrollDirection.UP:
            if (vertical)
                ws = activeWs.get_neighbor(Meta.MotionDirection.UP);
            else if (rtl)
                ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
            else
                ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
            break;
        case Clutter.ScrollDirection.DOWN:
            if (vertical)
                ws = activeWs.get_neighbor(Meta.MotionDirection.DOWN);
            else if (rtl)
                ws = activeWs.get_neighbor(Meta.MotionDirection.LEFT);
            else
                ws = activeWs.get_neighbor(Meta.MotionDirection.RIGHT);
            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;
        }
        this.actionMoveWorkspace(ws);

        this._canScroll = false;
        GLib.timeout_add(GLib.PRIORITY_DEFAULT,
            SCROLL_TIMEOUT_TIME, () => {
                this._canScroll = true;
                return GLib.SOURCE_REMOVE;
            });

        return Clutter.EVENT_STOP;
    }

    _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)
                return;

            this._resizePopup.destroy();
            this._resizePopup = null;
        }
    }
};
(uuay)gnome/*introspect.js�/* exported IntrospectService */
const { Gio, GLib, Meta, Shell, St } = imports.gi;

const APP_ALLOWLIST = [
    'org.freedesktop.impl.portal.desktop.gtk',
    'org.freedesktop.impl.portal.desktop.gnome',
];

const INTROSPECT_DBUS_API_VERSION = 3;

const { loadInterfaceXML } = imports.misc.fileUtils;
const { DBusSenderChecker } = imports.misc.util;

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();
                                });

        let tracker = Shell.WindowTracker.get_default();
        tracker.connect('notify::focus-app',
                        () => {
                            this._activeApplicationDirty = true;
                            this._syncRunningApplications();
                        });

        tracker.connect('tracked-windows-changed',
            () => this._dbusImpl.emit_signal('WindowsChanged', null));

        this._syncRunningApplications();

        this._senderChecker = new DBusSenderChecker(APP_ALLOWLIST);

        this._settings = St.Settings.get();
        this._settings.connect('notify::enable-animations',
            this._syncAnimationsEnabled.bind(this));
        this._syncAnimationsEnabled();

        const monitorManager = Meta.MonitorManager.get();
        monitorManager.connect('monitors-changed',
            this._syncScreenSize.bind(this));
        this._syncScreenSize();
    }

    _isStandaloneApp(app) {
        return app.get_windows().some(w => w.transient_for == null);
    }

    _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;
    }

    async GetRunningApplicationsAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        invocation.return_value(new GLib.Variant('(a{sa{sv}})', [this._runningApplications]));
    }

    async GetWindowsAsync(params, invocation) {
        let focusWindow = global.display.get_focus_window();
        let apps = this._appSystem.get_running();
        let windowsList = {};

        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            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);
        }
    }

    _syncScreenSize() {
        const oldScreenWidth = this._screenWidth;
        const oldScreenHeight = this._screenHeight;
        this._screenWidth = global.screen_width;
        this._screenHeight = global.screen_height;

        if (oldScreenWidth !== this._screenWidth ||
            oldScreenHeight !== this._screenHeight) {
            const variant = new GLib.Variant('(ii)',
                [this._screenWidth, this._screenHeight]);
            this._dbusImpl.emit_property_changed('ScreenSize', variant);
        }
    }

    get AnimationsEnabled() {
        return this._animationsEnabled;
    }

    get ScreenSize() {
        return [this._screenWidth, this._screenHeight];
    }

    get version() {
        return INTROSPECT_DBUS_API_VERSION;
    }
};
(uuay)mpris.jsm$/* exported MediaSection */
const { Gio, GObject, Shell, St } = imports.gi;
const Signals = imports.signals;

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 = GObject.registerClass(
class MediaMessage extends MessageList.Message {
    _init(player) {
        super._init('', '');

        this._player = player;

        this._icon = new St.Icon({ style_class: 'media-message-cover-icon' });
        this.setIcon(this._icon);

        // reclaim space used by unused elements
        this._secondaryBin.hide();
        this._closeButton.hide();

        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.connectObject(
            'changed', this._update.bind(this),
            'closed', this.close.bind(this), this);
        this._update();
    }

    vfunc_clicked() {
        this._player.raise();
        Main.panel.closeCalendar();
    }

    _updateNavButton(button, sensitive) {
        button.reactive = sensitive;
    }

    _update() {
        this.setTitle(this._player.trackTitle);
        this.setBody(this._player.trackArtists.join(', '));

        if (this._player.trackCoverUrl) {
            let file = Gio.File.new_for_uri(this._player.trackCoverUrl);
            this._icon.gicon = new Gio.FileIcon({ 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 = '';
        this._busName = busName;
    }

    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.disconnectObject(this);
        this._mprisProxy = null;

        this._playerProxy.disconnectObject(this);
        this._playerProxy = null;

        this.emit('closed');
    }

    _onMprisProxyReady() {
        this._mprisProxy.connectObject('notify::g-name-owner',
            () => {
                if (!this._mprisProxy.g_name_owner)
                    this._close();
            }, this);
        // It is possible for the bus to disappear before the previous signal
        // is connected, so we must ensure that the bus still exists at this
        // point.
        if (!this._mprisProxy.g_name_owner)
            this._close();
    }

    _onPlayerProxyReady() {
        this._playerProxy.connectObject(
            'g-properties-changed', () => this._updateState(), this);
        this._updateState();
    }

    _updateState() {
        let metadata = {};
        for (let prop in this._playerProxy.Metadata)
            metadata[prop] = this._playerProxy.Metadata[prop].deep_unpack();

        // Validate according to the spec; some clients send buggy metadata:
        // https://www.freedesktop.org/wiki/Specifications/mpris-spec/metadata
        this._trackArtists = metadata['xesam:artist'];
        if (!Array.isArray(this._trackArtists) ||
            !this._trackArtists.every(artist => typeof artist === 'string')) {
            if (typeof this._trackArtists !== 'undefined') {
                log(`Received faulty track artist metadata from ${
                    this._busName}; expected an array of strings, got ${
                    this._trackArtists} (${typeof this._trackArtists})`);
            }
            this._trackArtists =  [_("Unknown artist")];
        }

        this._trackTitle = metadata['xesam:title'];
        if (typeof this._trackTitle !== 'string') {
            if (typeof this._trackTitle !== 'undefined') {
                log(`Received faulty track title metadata from ${
                    this._busName}; expected a string, got ${
                    this._trackTitle} (${typeof this._trackTitle})`);
            }
            this._trackTitle = _("Unknown title");
        }

        this._trackCoverUrl = metadata['mpris:artUrl'];
        if (typeof this._trackCoverUrl !== 'string') {
            if (typeof this._trackCoverUrl !== 'undefined') {
                log(`Received faulty track cover art metadata from ${
                    this._busName}; expected a string, got ${
                    this._trackCoverUrl} (${typeof this._trackCoverUrl})`);
            }
            this._trackCoverUrl = '';
        }

        this.emit('changed');

        let visible = this._playerProxy.CanPlay;

        if (this._visible != visible) {
            this._visible = visible;
            if (visible)
                this.emit('show');
            else
                this.emit('hide');
        }
    }
};
Signals.addSignalMethods(MprisPlayer.prototype);

var MediaSection = GObject.registerClass(
class MediaSection extends MessageList.MessageListSection {
    _init() {
        super._init();

        this._players = new Map();

        this._proxy = new DBusProxy(Gio.DBus.session,
                                    'org.freedesktop.DBus',
                                    '/org/freedesktop/DBus',
                                    this._onProxyReady.bind(this));
    }

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

    _addPlayer(busName) {
        if (this._players.get(busName))
            return;

        let player = new MprisPlayer(busName);
        let message = null;
        player.connect('closed',
            () => {
                this._players.delete(busName);
            });
        player.connect('show', () => {
            message = new MediaMessage(player);
            this.addMessage(message, true);
        });
        player.connect('hide', () => {
            this.removeMessage(message, true);
            message = null;
        });

        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)ctrlAltTab.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CtrlAltTabManager */

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 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) {
        const 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] = a.proxy.get_transformed_position();
        let [bx] = 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 = new St.Icon({
                            gicon: textureCache.bind_cairo_surface_property(windows[i], 'icon'),
                            icon_size: POPUP_APPICON_SIZE,
                        });
                    }
                }

                items.push({
                    name: windows[i].title,
                    proxy: windows[i].get_compositor_private(),
                    focusCallback: timestamp => {
                        Main.activateWindow(windows[i], timestamp);
                    },
                    iconActor: icon,
                    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.KEY_Left)
            this._select(this._previous());
        else if (keysym == Clutter.KEY_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) {
        const 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_child(icon);

        let text = new St.Label({
            text: item.name,
            x_align: Clutter.ActorAlign.CENTER,
        });
        box.add_child(text);

        this.addItem(box, text);
    }
});
(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 = '42.9';
/* 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 soup2 should be used instead of soup3, 0 otherwise */
var HAVE_SOUP2 = 1;
/* 1 if recorder is enabled, 0 otherwise */
var HAVE_RECORDER = 1;
/* 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';
/* g-i package versions */
var LIBMUTTER_API_VERSION = '10'
(uuay)unlockDialog.js�n// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported UnlockDialog */

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

const Background = imports.ui.background;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const SwipeTracker = imports.ui.swipeTracker;

const AuthPrompt = imports.gdm.authPrompt;

// The timeout before going back automatically to the lock screen (in seconds)
const IDLE_TIMEOUT = 2 * 60;

// The timeout before showing the unlock hint (in seconds)
const HINT_TIMEOUT = 4;

const CROSSFADE_TIME = 300;
const FADE_OUT_TRANSLATION = 200;
const FADE_OUT_SCALE = 0.3;

const BLUR_BRIGHTNESS = 0.55;
const BLUR_SIGMA = 60;

const SUMMARY_ICON_SIZE = 32;

var NotificationsBox = GObject.registerClass({
    Signals: { 'wake-up-screen': {} },
}, class NotificationsBox extends St.BoxLayout {
    _init() {
        super._init({
            vertical: true,
            name: 'unlockDialogNotifications',
            style_class: 'unlock-dialog-notifications-container',
        });

        this._scrollView = new St.ScrollView({ hscrollbar_policy: St.PolicyType.NEVER });
        this._notificationBox = new St.BoxLayout({
            vertical: true,
            style_class: 'unlock-dialog-notifications-container',
        });
        this._scrollView.add_actor(this._notificationBox);

        this.add_child(this._scrollView);

        this._settings = new Gio.Settings({
            schema_id: 'org.gnome.desktop.notifications',
        });

        this._sources = new Map();
        Main.messageTray.getSources().forEach(source => {
            this._sourceAdded(Main.messageTray, source, true);
        });
        this._updateVisibility();

        Main.messageTray.connectObject('source-added',
            this._sourceAdded.bind(this), this);

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

    _onDestroy() {
        let items = this._sources.entries();
        for (let [source, obj] of items)
            this._removeSource(source, obj);
    }

    _updateVisibility() {
        this._notificationBox.visible =
            this._notificationBox.get_children().some(a => a.visible);

        this.visible = this._notificationBox.visible;
    }

    _makeNotificationSource(source, box) {
        let sourceActor = new MessageTray.SourceActor(source, SUMMARY_ICON_SIZE);
        box.add_child(sourceActor);

        let textBox = new St.BoxLayout({
            x_expand: true,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        box.add_child(textBox);

        let title = new St.Label({
            text: source.title,
            style_class: 'unlock-dialog-notification-label',
            x_expand: true,
            x_align: Clutter.ActorAlign.START,
        });
        textBox.add(title);

        let count = source.unseenCount;
        let countLabel = new St.Label({
            text: `${count}`,
            visible: count > 1,
            style_class: 'unlock-dialog-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({ child: sourceActor });
        box.add(sourceBin);

        let textBox = new St.BoxLayout({ vertical: true });
        box.add_child(textBox);

        let title = new St.Label({
            text: source.title.replace(/\n/g, ' '),
            style_class: 'unlock-dialog-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) {
                const bodyText = n.bannerBodyText.replace(/\n/g, ' ');
                body = n.bannerBodyMarkup
                    ? bodyText
                    : GLib.markup_escape_text(bodyText, -1);
            }

            let label = new St.Label({ style_class: 'unlock-dialog-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;
    }

    _updateSourceBoxStyle(source, obj, box) {
        let hasCriticalNotification =
            source.notifications.some(n => n.urgency === MessageTray.Urgency.CRITICAL);

        if (hasCriticalNotification !== obj.hasCriticalNotification) {
            obj.hasCriticalNotification = hasCriticalNotification;

            if (hasCriticalNotification)
                box.add_style_class_name('critical');
            else
                box.remove_style_class_name('critical');
        }
    }

    _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);

        this._updateSourceBoxStyle(source, obj, box);
    }

    _wakeUpScreenForSource(source) {
        if (!this._settings.get_boolean('show-banners'))
            return;
        const obj = this._sources.get(source);
        if (obj?.sourceBox.visible)
            this.emit('wake-up-screen');
    }

    _sourceAdded(tray, source, initial) {
        let obj = {
            visible: source.policy.showInLockScreen,
            detailed: this._shouldShowDetails(source),
            sourceBox: null,
            titleLabel: null,
            countLabel: null,
            hasCriticalNotification: false,
        };

        obj.sourceBox = new St.BoxLayout({
            style_class: 'unlock-dialog-notification-source',
            x_expand: true,
        });
        this._showSource(source, obj, obj.sourceBox);
        this._notificationBox.add_child(obj.sourceBox);

        source.connectObject(
            'notify::count', () => this._countChanged(source, obj),
            'notify::title', () => this._titleChanged(source, obj),
            'destroy', () => {
                this._removeSource(source, obj);
                this._updateVisibility();
            }, this);
        obj.policyChangedId = source.policy.connect('notify', (policy, pspec) => {
            if (pspec.name === 'show-in-lock-screen')
                this._visibleChanged(source, obj);
            else
                this._detailedChanged(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;
            widget.ease({
                height: natHeight,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                duration: 250,
                onComplete: () => {
                    this._scrollView.vscrollbar_policy = St.PolicyType.AUTOMATIC;
                    widget.set_height(-1);
                },
            });

            this._updateVisibility();
            this._wakeUpScreenForSource(source);
        }
    }

    _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 = `${count}`;
            obj.countLabel.visible = count > 1;
        }

        obj.sourceBox.visible = obj.visible && (source.unseenCount > 0);

        this._updateVisibility();
        this._wakeUpScreenForSource(source);
    }

    _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();
        this._wakeUpScreenForSource(source);
    }

    _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);
    }

    _removeSource(source, obj) {
        obj.sourceBox.destroy();
        obj.sourceBox = obj.titleLabel = obj.countLabel = null;

        source.policy.disconnect(obj.policyChangedId);

        this._sources.delete(source);
    }
});

var Clock = GObject.registerClass(
class UnlockDialogClock extends St.BoxLayout {
    _init() {
        super._init({ style_class: 'unlock-dialog-clock', vertical: true });

        this._time = new St.Label({
            style_class: 'unlock-dialog-clock-time',
            x_align: Clutter.ActorAlign.CENTER,
        });
        this._date = new St.Label({
            style_class: 'unlock-dialog-clock-date',
            x_align: Clutter.ActorAlign.CENTER,
        });
        this._hint = new St.Label({
            style_class: 'unlock-dialog-clock-hint',
            x_align: Clutter.ActorAlign.CENTER,
            opacity: 0,
        });

        this.add_child(this._time);
        this.add_child(this._date);
        this.add_child(this._hint);

        this._wallClock = new GnomeDesktop.WallClock({ time_only: true });
        this._wallClock.connect('notify::clock', this._updateClock.bind(this));

        this._seat = Clutter.get_default_backend().get_default_seat();
        this._seat.connectObject('notify::touch-mode',
            this._updateHint.bind(this), this);

        this._monitorManager = Meta.MonitorManager.get();
        this._monitorManager.connectObject('power-save-mode-changed',
            () => (this._hint.opacity = 0), this);

        this._idleMonitor = global.backend.get_core_idle_monitor();
        this._idleWatchId = this._idleMonitor.add_idle_watch(HINT_TIMEOUT * 1000, () => {
            this._hint.ease({
                opacity: 255,
                duration: CROSSFADE_TIME,
            });
        });

        this._updateClock();
        this._updateHint();

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

    _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);
    }

    _updateHint() {
        this._hint.text = this._seat.touch_mode
            ? _('Swipe up to unlock')
            : _('Click or press a key to unlock');
    }

    _onDestroy() {
        this._wallClock.run_dispose();

        this._idleMonitor.remove_watch(this._idleWatchId);
    }
});

var UnlockDialogLayout = GObject.registerClass(
class UnlockDialogLayout extends Clutter.LayoutManager {
    _init(stack, notifications, switchUserButton) {
        super._init();

        this._stack = stack;
        this._notifications = notifications;
        this._switchUserButton = switchUserButton;
    }

    vfunc_get_preferred_width(container, forHeight) {
        return this._stack.get_preferred_width(forHeight);
    }

    vfunc_get_preferred_height(container, forWidth) {
        return this._stack.get_preferred_height(forWidth);
    }

    vfunc_allocate(container, box) {
        let [width, height] = box.get_size();

        let tenthOfHeight = height / 10.0;
        let thirdOfHeight = height / 3.0;

        let [, , stackWidth, stackHeight] =
            this._stack.get_preferred_size();

        let [, , notificationsWidth, notificationsHeight] =
            this._notifications.get_preferred_size();

        let columnWidth = Math.max(stackWidth, notificationsWidth);

        let columnX1 = Math.floor((width - columnWidth) / 2.0);
        let actorBox = new Clutter.ActorBox();

        // Notifications
        let maxNotificationsHeight = Math.min(
            notificationsHeight,
            height - tenthOfHeight - stackHeight);

        actorBox.x1 = columnX1;
        actorBox.y1 = height - maxNotificationsHeight;
        actorBox.x2 = columnX1 + columnWidth;
        actorBox.y2 = actorBox.y1 + maxNotificationsHeight;

        this._notifications.allocate(actorBox);

        // Authentication Box
        let stackY = Math.min(
            thirdOfHeight,
            height - stackHeight - maxNotificationsHeight);

        actorBox.x1 = columnX1;
        actorBox.y1 = stackY;
        actorBox.x2 = columnX1 + columnWidth;
        actorBox.y2 = stackY + stackHeight;

        this._stack.allocate(actorBox);

        // Switch User button
        if (this._switchUserButton.visible) {
            let [, , natWidth, natHeight] =
                this._switchUserButton.get_preferred_size();

            const textDirection = this._switchUserButton.get_text_direction();
            if (textDirection === Clutter.TextDirection.RTL)
                actorBox.x1 = box.x1 + natWidth;
            else
                actorBox.x1 = box.x2 - (natWidth * 2);

            actorBox.y1 = box.y2 - (natHeight * 2);
            actorBox.x2 = actorBox.x1 + natWidth;
            actorBox.y2 = actorBox.y1 + natHeight;

            this._switchUserButton.allocate(actorBox);
        }
    }
});

var UnlockDialog = GObject.registerClass({
    Signals: {
        'failed': {},
        'wake-up-screen': {},
    },
}, class UnlockDialog extends St.Widget {
    _init(parentActor) {
        super._init({
            accessible_role: Atk.Role.WINDOW,
            style_class: 'unlock-dialog',
            visible: false,
            reactive: true,
        });

        parentActor.add_child(this);

        this._gdmClient = new Gdm.Client();

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

        this._adjustment = new St.Adjustment({
            actor: this,
            lower: 0,
            upper: 2,
            page_size: 1,
            page_increment: 1,
        });
        this._adjustment.connect('notify::value', () => {
            this._setTransitionProgress(this._adjustment.value);
        });

        this._swipeTracker = new SwipeTracker.SwipeTracker(this,
            Clutter.Orientation.VERTICAL,
            Shell.ActionMode.UNLOCK_SCREEN);
        this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
        this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
        this._swipeTracker.connect('end', this._swipeEnd.bind(this));

        this.connect('scroll-event', (o, event) => {
            if (this._swipeTracker.canHandleScrollEvent(event))
                return Clutter.EVENT_PROPAGATE;

            let direction = event.get_scroll_direction();
            if (direction === Clutter.ScrollDirection.UP)
                this._showClock();
            else if (direction === Clutter.ScrollDirection.DOWN)
                this._showPrompt();
            return Clutter.EVENT_STOP;
        });

        this._activePage = null;

        let tapAction = new Clutter.TapAction();
        tapAction.connect('tap', this._showPrompt.bind(this));
        this.add_action(tapAction);

        // Background
        this._backgroundGroup = new Clutter.Actor();
        this.add_child(this._backgroundGroup);

        this._bgManagers = [];

        const themeContext = St.ThemeContext.get_for_stage(global.stage);
        themeContext.connectObject('notify::scale-factor',
            () => this._updateBackgroundEffects(), this);

        this._updateBackgrounds();
        Main.layoutManager.connectObject('monitors-changed',
            this._updateBackgrounds.bind(this), this);

        this._userManager = AccountsService.UserManager.get_default();
        this._userName = GLib.get_user_name();
        this._user = this._userManager.get_user(this._userName);

        // Authentication & Clock stack
        this._stack = new Shell.Stack();

        this._promptBox = new St.BoxLayout({ vertical: true });
        this._promptBox.set_pivot_point(0.5, 0.5);
        this._promptBox.hide();
        this._stack.add_child(this._promptBox);

        this._clock = new Clock();
        this._clock.set_pivot_point(0.5, 0.5);
        this._stack.add_child(this._clock);
        this._showClock();

        this.allowCancel = false;

        Main.ctrlAltTabManager.addGroup(this, _('Unlock Window'), 'dialog-password-symbolic');

        // Notifications
        this._notificationsBox = new NotificationsBox();
        this._notificationsBox.connect('wake-up-screen', () => this.emit('wake-up-screen'));

        // Switch User button
        this._otherUserButton = new St.Button({
            style_class: 'modal-dialog-button button switch-user-button',
            accessible_name: _('Log in as another user'),
            button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
            reactive: false,
            opacity: 0,
            x_align: Clutter.ActorAlign.END,
            y_align: Clutter.ActorAlign.END,
            child: new St.Icon({ icon_name: 'system-users-symbolic' }),
        });
        this._otherUserButton.set_pivot_point(0.5, 0.5);
        this._otherUserButton.connect('clicked', this._otherUserClicked.bind(this));

        this._screenSaverSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.screensaver' });

        this._screenSaverSettings.connectObject('changed::user-switch-enabled',
            this._updateUserSwitchVisibility.bind(this), this);

        this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });
        this._lockdownSettings.connect('changed::disable-user-switching',
            this._updateUserSwitchVisibility.bind(this));

        this._user.connectObject('notify::is-loaded',
            this._updateUserSwitchVisibility.bind(this), this);

        this._updateUserSwitchVisibility();

        // Main Box
        let mainBox = new St.Widget();
        mainBox.add_constraint(new Layout.MonitorConstraint({ primary: true }));
        mainBox.add_child(this._stack);
        mainBox.add_child(this._notificationsBox);
        mainBox.add_child(this._otherUserButton);
        mainBox.layout_manager = new UnlockDialogLayout(
            this._stack,
            this._notificationsBox,
            this._otherUserButton);
        this.add_child(mainBox);

        this._idleMonitor = global.backend.get_core_idle_monitor();
        this._idleWatchId = this._idleMonitor.add_idle_watch(IDLE_TIMEOUT * 1000, this._escape.bind(this));

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

    vfunc_key_press_event(keyEvent) {
        if (this._activePage === this._promptBox ||
            (this._promptBox && this._promptBox.visible))
            return Clutter.EVENT_PROPAGATE;

        const { keyval } = keyEvent;
        if (keyval === Clutter.KEY_Shift_L ||
            keyval === Clutter.KEY_Shift_R ||
            keyval === Clutter.KEY_Shift_Lock ||
            keyval === Clutter.KEY_Caps_Lock)
            return Clutter.EVENT_PROPAGATE;

        let unichar = keyEvent.unicode_value;

        this._showPrompt();

        if (GLib.unichar_isgraph(unichar))
            this._authPrompt.addCharacter(unichar);

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_captured_event(event) {
        if (Main.keyboard.maybeHandleEvent(event))
            return Clutter.EVENT_STOP;

        return Clutter.EVENT_PROPAGATE;
    }

    _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,
            effect: new Shell.BlurEffect({ name: 'blur' }),
        });

        let bgManager = new Background.BackgroundManager({
            container: widget,
            monitorIndex,
            controlPosition: false,
        });

        this._bgManagers.push(bgManager);

        this._backgroundGroup.add_child(widget);
    }

    _updateBackgroundEffects() {
        const themeContext = St.ThemeContext.get_for_stage(global.stage);

        for (const widget of this._backgroundGroup) {
            const effect = widget.get_effect('blur');

            if (effect) {
                effect.set({
                    brightness: BLUR_BRIGHTNESS,
                    sigma: BLUR_SIGMA * themeContext.scale_factor,
                });
            }
        }
    }

    _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);
        this._updateBackgroundEffects();
    }

    _ensureAuthPrompt() {
        if (!this._authPrompt) {
            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._promptBox.add_child(this._authPrompt);
        }

        this._authPrompt.reset();
        this._authPrompt.updateSensitivity(true);
    }

    _maybeDestroyAuthPrompt() {
        let focus = global.stage.key_focus;
        if (focus === null ||
            (this._authPrompt && this._authPrompt.contains(focus)) ||
            (this._otherUserButton && focus === this._otherUserButton))
            this.grab_key_focus();

        if (this._authPrompt) {
            this._authPrompt.destroy();
            this._authPrompt = null;
        }
    }

    _showClock() {
        if (this._activePage === this._clock)
            return;

        this._activePage = this._clock;

        this._adjustment.ease(0, {
            duration: CROSSFADE_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this._maybeDestroyAuthPrompt(),
        });
    }

    _showPrompt() {
        this._ensureAuthPrompt();

        if (this._activePage === this._promptBox)
            return;

        this._activePage = this._promptBox;

        this._adjustment.ease(1, {
            duration: CROSSFADE_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    _setTransitionProgress(progress) {
        this._promptBox.visible = progress > 0;
        this._clock.visible = progress < 1;

        this._otherUserButton.set({
            reactive: progress > 0,
            can_focus: progress > 0,
        });

        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);

        this._promptBox.set({
            opacity: 255 * progress,
            scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
            scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
            translation_y: FADE_OUT_TRANSLATION * (1 - progress) * scaleFactor,
        });

        this._clock.set({
            opacity: 255 * (1 - progress),
            scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * (1 - progress),
            scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * (1 - progress),
            translation_y: -FADE_OUT_TRANSLATION * progress * scaleFactor,
        });

        this._otherUserButton.set({
            opacity: 255 * progress,
            scale_x: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
            scale_y: FADE_OUT_SCALE + (1 - FADE_OUT_SCALE) * progress,
        });
    }

    _fail() {
        this._showClock();
        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 });
    }

    _escape() {
        if (this._authPrompt && this.allowCancel)
            this._authPrompt.cancel();
    }

    _swipeBegin(tracker, monitor) {
        if (monitor !== Main.layoutManager.primaryIndex)
            return;

        this._adjustment.remove_transition('value');

        this._ensureAuthPrompt();

        let progress = this._adjustment.value;
        tracker.confirmSwipe(this._stack.height,
            [0, 1],
            progress,
            Math.round(progress));
    }

    _swipeUpdate(tracker, progress) {
        this._adjustment.value = progress;
    }

    _swipeEnd(tracker, duration, endProgress) {
        this._activePage = endProgress
            ? this._promptBox
            : this._clock;

        this._adjustment.ease(endProgress, {
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            duration,
            onComplete: () => {
                if (this._activePage === this._clock)
                    this._maybeDestroyAuthPrompt();
            },
        });
    }

    _otherUserClicked() {
        Gdm.goto_login_session_sync(null);

        this._authPrompt.cancel();
    }

    _onDestroy() {
        this.popModal();

        if (this._idleWatchId) {
            this._idleMonitor.remove_watch(this._idleWatchId);
            this._idleWatchId = 0;
        }

        if (this._gdmClient) {
            this._gdmClient = null;
            delete this._gdmClient;
        }
    }

    _updateUserSwitchVisibility() {
        this._otherUserButton.visible = this._userManager.can_switch() &&
            this._screenSaverSettings.get_boolean('user-switch-enabled') &&
            !this._lockdownSettings.get_boolean('disable-user-switching');
    }

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

    finish(onComplete) {
        if (!this._authPrompt) {
            onComplete();
            return;
        }

        this._authPrompt.finish(onComplete);
    }

    open(timestamp) {
        this.show();

        if (this._isModal)
            return true;

        let modalParams = {
            timestamp,
            actionMode: Shell.ActionMode.UNLOCK_SCREEN,
        };
        let grab = Main.pushModal(Main.uiGroup, modalParams);
        if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
            Main.popModal(grab);
            return false;
        }

        this._grab = grab;
        this._isModal = true;

        return true;
    }

    activate() {
        this._showPrompt();
    }

    popModal(timestamp) {
        if (this._isModal) {
            Main.popModal(this._grab, timestamp);
            this._grab = null;
            this._isModal = false;
        }
    }
});
(uuay)modemManager.js�'// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ModemBase, ModemGsm, ModemCdma, BroadbandModem  */

const { Gio, GObject, NM, NMA } = imports.gi;

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:
// @operatorName: operator name
// @operatorCode: 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(operatorName, operatorCode) {
    if (operatorName) {
        if (operatorName.length != 0 &&
            (operatorName.length > 6 || operatorName.length < 5)) {
            // this looks like a valid name, i.e. not an MCCMNC (that some
            // devices return when not yet connected
            return operatorName;
        }

        if (isNaN(parseInt(operatorName))) {
            // name is definitely not a MCCMNC, so it may be a name
            // after all; return that
            return operatorName;
        }
    }

    let needle;
    if ((!operatorName || operatorName.length == 0) && operatorCode)
        needle = operatorCode;
    else if (operatorName && (operatorName.length == 6 || operatorName.length == 5))
        needle = operatorName;
    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)
        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 ModemBase = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
    Properties: {
        'operator-name': GObject.ParamSpec.string(
            'operator-name', 'operator-name', 'operator-name',
            GObject.ParamFlags.READABLE,
            null),
        'signal-quality': GObject.ParamSpec.int(
            'signal-quality', 'signal-quality', 'signal-quality',
            GObject.ParamFlags.READABLE,
            0, 100, 0),
    },
}, class ModemBase extends GObject.Object {
    _init() {
        super._init();
        this._operatorName = null;
        this._signalQuality = 0;
    }

    get operatorName() {
        return this._operatorName;
    }

    get signalQuality() {
        return this._signalQuality;
    }

    _setOperatorName(operatorName) {
        if (this._operatorName == operatorName)
            return;
        this._operatorName = operatorName;
        this.notify('operator-name');
    }

    _setSignalQuality(signalQuality) {
        if (this._signalQuality == signalQuality)
            return;
        this._signalQuality = signalQuality;
        this.notify('signal-quality');
    }
});

var ModemGsm = GObject.registerClass(
class ModemGsm extends ModemBase {
    _init(path) {
        super._init();
        this._proxy = new ModemGsmNetworkProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);

        // Code is duplicated because the function have different signatures
        this._proxy.connectSignal('SignalQuality', (proxy, sender, [quality]) => {
            this._setSignalQuality(quality);
        });
        this._proxy.connectSignal('RegistrationInfo', (proxy, sender, [_status, code, name]) => {
            this._setOperatorName(_findProviderForMccMnc(name, code));
        });
        this._proxy.GetRegistrationInfoRemote(([result], err) => {
            if (err) {
                log(err);
                return;
            }

            let [status_, code, name] = result;
            this._setOperatorName(_findProviderForMccMnc(name, code));
        });
        this._proxy.GetSignalQualityRemote((result, err) => {
            if (err) {
                // it will return an error if the device is not connected
                this._setSignalQuality(0);
            } else {
                let [quality] = result;
                this._setSignalQuality(quality);
            }
        });
    }
});

var ModemCdma = GObject.registerClass(
class ModemCdma extends ModemBase {
    _init(path) {
        super._init();
        this._proxy = new ModemCdmaProxy(Gio.DBus.system, 'org.freedesktop.ModemManager', path);

        this._proxy.connectSignal('SignalQuality', (proxy, sender, params) => {
            this._setSignalQuality(params[0]);

            // 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._setSignalQuality(0);
            } else {
                let [quality] = result;
                this._setSignalQuality(quality);
            }
        });
    }

    _refreshServingSystem() {
        this._proxy.GetServingSystemRemote(([result], err) => {
            if (err) {
                // it will return an error if the device is not connected
                this._setOperatorName(null);
            } else {
                let [bandClass_, band_, sid] = result;
                this._setOperatorName(_findProviderForSid(sid));
            }
        });
    }
});


// ------------------------------------------------------- //
// 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 = GObject.registerClass({
    Properties: {
        'capabilities': GObject.ParamSpec.flags(
            'capabilities', 'capabilities', 'capabilities',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            NM.DeviceModemCapabilities.$gtype,
            NM.DeviceModemCapabilities.NONE),
    },
}, class BroadbandModem extends ModemBase {
    _init(path, capabilities) {
        super._init({ 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._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._setSignalQuality(quality);
    }

    _reloadOperatorName() {
        let newName = "";
        if (this.operator_name_3gpp && this.operator_name_3gpp.length > 0)
            newName += this.operator_name_3gpp;

        if (this.operator_name_cdma && this.operator_name_cdma.length > 0) {
            if (newName != "")
                newName += ", ";
            newName += this.operator_name_cdma;
        }

        this._setOperatorName(newName);
    }

    _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();
    }
});
(uuay)oVirt.js4// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported getOVirtCredentialsManager */

const Gio = imports.gi.Gio;
const Signals = imports.signals;
const Credential = imports.gdm.credentialManager;

var SERVICE_NAME = 'gdm-ovirtcred';

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 OVirtCredentialsManager extends Credential.CredentialManager {
    constructor() {
        super(SERVICE_NAME);
        this._credentials = new OVirtCredentials();
        this._credentials.connectSignal('UserAuthenticated',
            (proxy, sender, [token]) => {
                this.token = token;
            });
    }
};
Signals.addSignalMethods(OVirtCredentialsManager.prototype);

function getOVirtCredentialsManager() {
    if (!_oVirtCredentialsManager)
        _oVirtCredentialsManager = new OVirtCredentialsManager();

    return _oVirtCredentialsManager;
}
(uuay)messageTray.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported NotificationPolicy, NotificationGenericPolicy,
   NotificationApplicationPolicy, Source, SourceActor,
   SystemNotificationSource, MessageTray */

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

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 SignalTracker = imports.misc.signalTracker;

const SHELL_KEYBINDINGS_SCHEMA = 'org.gnome.shell.keybindings';

var ANIMATION_TIME = 200;
var NOTIFICATION_TIMEOUT = 4000;

var HIDE_TIMEOUT = 200;
var LONGER_HIDE_TIMEOUT = 600;

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._focused = false;
    }

    grabFocus() {
        if (this._focused)
            return;

        this._prevKeyFocusActor = global.stage.get_key_focus();

        global.stage.connectObject('notify::key-focus',
            this._focusActorChanged.bind(this), 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;

        global.stage.disconnectObject(this);

        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 = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
    Properties: {
        'enable': GObject.ParamSpec.boolean(
            'enable', 'enable', 'enable', GObject.ParamFlags.READABLE, true),
        'enable-sound': GObject.ParamSpec.boolean(
            'enable-sound', 'enable-sound', 'enable-sound',
            GObject.ParamFlags.READABLE, true),
        'show-banners': GObject.ParamSpec.boolean(
            'show-banners', 'show-banners', 'show-banners',
            GObject.ParamFlags.READABLE, true),
        'force-expanded': GObject.ParamSpec.boolean(
            'force-expanded', 'force-expanded', 'force-expanded',
            GObject.ParamFlags.READABLE, false),
        'show-in-lock-screen': GObject.ParamSpec.boolean(
            'show-in-lock-screen', 'show-in-lock-screen', 'show-in-lock-screen',
            GObject.ParamFlags.READABLE, false),
        'details-in-lock-screen': GObject.ParamSpec.boolean(
            'details-in-lock-screen', 'details-in-lock-screen', 'details-in-lock-screen',
            GObject.ParamFlags.READABLE, false),
    },
}, class NotificationPolicy extends GObject.Object {
    // Do nothing for the default policy. These methods are only useful for the
    // GSettings policy.
    store() { }

    destroy() {
        this.run_dispose();
    }

    get enable() {
        return true;
    }

    get enableSound() {
        return true;
    }

    get showBanners() {
        return true;
    }

    get forceExpanded() {
        return false;
    }

    get showInLockScreen() {
        return false;
    }

    get detailsInLockScreen() {
        return false;
    }
});

var NotificationGenericPolicy = GObject.registerClass({
}, class NotificationGenericPolicy extends NotificationPolicy {
    _init() {
        super._init();
        this.id = 'generic';

        this._masterSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.notifications' });
        this._masterSettings.connect('changed', this._changed.bind(this));
    }

    destroy() {
        this._masterSettings.run_dispose();

        super.destroy();
    }

    _changed(settings, key) {
        if (this.constructor.find_property(key))
            this.notify(key);
    }

    get showBanners() {
        return this._masterSettings.get_boolean('show-banners');
    }

    get showInLockScreen() {
        return this._masterSettings.get_boolean('show-in-lock-screen');
    }
});

var NotificationApplicationPolicy = GObject.registerClass({
}, class NotificationApplicationPolicy extends NotificationPolicy {
    _init(id) {
        super._init();

        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.includes(this._canonicalId)) {
            apps.push(this._canonicalId);
            this._masterSettings.set_strv('application-children', apps);
        }
    }

    destroy() {
        this._masterSettings.run_dispose();
        this._settings.run_dispose();

        super.destroy();
    }

    _changed(settings, key) {
        if (this.constructor.find_property(key))
            this.notify(key);
    }

    _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 = GObject.registerClass({
    Properties: {
        'acknowledged': GObject.ParamSpec.boolean(
            'acknowledged', 'acknowledged', 'acknowledged',
            GObject.ParamFlags.READWRITE,
            false),
    },
    Signals: {
        'activated': {},
        'destroy': { param_types: [GObject.TYPE_UINT] },
        'updated': { param_types: [GObject.TYPE_BOOLEAN] },
    },
}, class Notification extends GObject.Object {
    _init(source, title, banner, params) {
        super._init();

        this.source = source;
        this.title = title;
        this.urgency = Urgency.NORMAL;
        // '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.bannerBodyText = null;
        this.bannerBodyMarkup = false;
        this._soundName = null;
        this._soundFile = null;
        this._soundPlayed = false;
        this.actions = [];
        this.setResident(false);

        // 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, callback });
    }

    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 = NotificationDestroyedReason.DISMISSED) {
        this.emit('destroy', reason);
        this.run_dispose();
    }
});
SignalTracker.registerDestroyableType(Notification);

var NotificationBanner = GObject.registerClass({
    Signals: {
        'done-displaying': {},
        'unfocused': {},
    },
}, class NotificationBanner extends Calendar.NotificationMessage {
    _init(notification) {
        super._init(notification);

        this.can_focus = false;
        this.add_style_class_name('notification-banner');

        this._buttonBox = null;

        this._addActions();
        this._addSecondaryIcon();

        this.notification.connectObject('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');
        }, this);
    }

    _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) {
            const 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) {
        const button = new St.Button({
            style_class: 'notification-button',
            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.connect('destroy',
            () => (this._actorDestroyed = true));
        this._actorDestroyed = false;

        let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        this._iconBin = new St.Bin({
            x_expand: true,
            height: size * scaleFactor,
            width: size * scaleFactor,
        });

        this.add_actor(this._iconBin);

        this._source.connectObject('icon-updated',
            this._updateIcon.bind(this), 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 Source = GObject.registerClass({
    Properties: {
        'count': GObject.ParamSpec.int(
            'count', 'count', 'count',
            GObject.ParamFlags.READABLE,
            0, GLib.MAXINT32, 0),
        'policy': GObject.ParamSpec.object(
            'policy', 'policy', 'policy',
            GObject.ParamFlags.READWRITE,
            NotificationPolicy.$gtype),
        'title': GObject.ParamSpec.string(
            'title', 'title', 'title',
            GObject.ParamFlags.READWRITE,
            null),
    },
    Signals: {
        'destroy': { param_types: [GObject.TYPE_UINT] },
        'icon-updated': {},
        'notification-added': { param_types: [Notification.$gtype] },
        'notification-show': { param_types: [Notification.$gtype] },
    },
}, class Source extends GObject.Object {
    _init(title, iconName) {
        super._init({ title });

        this.SOURCE_ICON_SIZE = 48;

        this.iconName = iconName;

        this.isChat = false;

        this.notifications = [];

        this._policy = this._createPolicy();
    }

    get policy() {
        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.notify('count');
    }

    _createPolicy() {
        return new NotificationGenericPolicy();
    }

    get narrowestPrivacyScope() {
        return this.notifications.every(n => n.privacyScope == PrivacyScope.SYSTEM)
            ? PrivacyScope.SYSTEM
            : PrivacyScope.USER;
    }

    setTitle(newTitle) {
        if (this.title == newTitle)
            return;

        this.title = newTitle;
        this.notify('title');
    }

    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);
        this.countUpdated();

        if (this.notifications.length == 0)
            this.destroy();
    }

    pushNotification(notification) {
        if (this.notifications.includes(notification))
            return;

        while (this.notifications.length >= MAX_NOTIFICATIONS_PER_SOURCE)
            this.notifications.shift().destroy(NotificationDestroyedReason.EXPIRED);

        notification.connect('destroy', this._onNotificationDestroy.bind(this));
        notification.connect('notify::acknowledged', this.countUpdated.bind(this));
        this.notifications.push(notification);
        this.emit('notification-added', notification);

        this.countUpdated();
    }

    showNotification(notification) {
        notification.acknowledged = false;
        this.pushNotification(notification);

        if (notification.urgency === Urgency.LOW)
            return;

        if (this.policy.showBanners || notification.urgency == Urgency.CRITICAL)
            this.emit('notification-show', notification);
    }

    destroy(reason) {
        let notifications = this.notifications;
        this.notifications = [];

        for (let i = 0; i < notifications.length; i++)
            notifications[i].destroy(reason);

        this.emit('destroy', reason);

        this.policy.destroy();
        this.run_dispose();
    }

    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();
        }
    }
});
SignalTracker.registerDestroyableType(Source);

var MessageTray = GObject.registerClass({
    Signals: {
        'queue-changed': {},
        'source-added': { param_types: [Source.$gtype] },
        'source-removed': { param_types: [Source.$gtype] },
    },
}, class MessageTray extends St.Widget {
    _init() {
        super._init({
            visible: false,
            clip_to_allocation: true,
            layout_manager: new Clutter.BinLayout(),
        });

        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);
        });

        let constraint = new Layout.MonitorConstraint({ primary: true });
        Main.layoutManager.panelBox.bind_property('visible',
                                                  constraint, 'work-area',
                                                  GObject.BindingFlags.SYNC_CREATE);
        this.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.add_actor(this._bannerBin);

        this._notificationFocusGrabber = new FocusGrabber(this._bannerBin);
        this._notificationQueue = [];
        this._notification = null;
        this._banner = null;

        this._userActiveWhileNotificationShown = false;

        this.idleMonitor = global.backend.get_core_idle_monitor();

        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, { 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 Set();

        this._sessionUpdated();
    }

    _sessionUpdated() {
        this._updateState();
    }

    _onDragBegin() {
        Shell.util_set_hidden_from_pick(this, true);
    }

    _onDragEnd() {
        Shell.util_set_hidden_from_pick(this, 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('notify::enable', () => {
            this._onSourceEnableChanged(source.policy, source);
        });
        source.policy.connect('notify', this._updateState.bind(this));
        this._onSourceEnableChanged(source.policy, source);
    }

    _addSource(source) {
        this._sources.add(source);

        source.connectObject(
            'notification-show', this._onNotificationShow.bind(this),
            'destroy', () => this._removeSource(source), this);

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

    _removeSource(source) {
        this._sources.delete(source);
        source.disconnectObject(this);
        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);
        }
    }

    _onNotificationDestroy(notification) {
        this._notificationRemoved = this._notification === notification;

        if (this._notificationRemoved) {
            if (this._notificationState === State.SHOWN ||
                this._notificationState === State.SHOWING) {
                this._updateNotificationTimeout(0);
                this._updateState();
            }
        } else {
            const index = this._notificationQueue.indexOf(notification);
            if (index !== -1) {
                this._notificationQueue.splice(index, 1);
                this.emit('queue-changed');
            }
        }
    }

    _onNotificationShow(_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.includes(notification)) {
            // 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) {
            GLib.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] = 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 : HIDE_TIMEOUT;
            this._notificationLeftTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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] = 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 = GLib.timeout_add(
                GLib.PRIORITY_DEFAULT,
                LONGER_HIDE_TIMEOUT,
                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.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 ||= 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.SHOWING ||
                   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._notificationState === State.SHOWN &&
                       this._pointerInNotification) {
                if (!this._banner.expanded)
                    this._expandBanner(false);
                else
                    this._ensureBannerFocused();
            }
        }

        this._updatingState = false;

        // Clean transient variables that are used to communicate actions
        // to updateState()
        this._notificationExpired = false;
    }

    _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._banner.connectObject(
            'done-displaying', this._escapeTray.bind(this),
            'unfocused', () => this._updateState(), this);

        this._bannerBin.add_actor(this._banner);

        this._bannerBin.opacity = 0;
        this._bannerBin.y = -this._banner.height;
        this.show();

        Meta.disable_unredirect_for_display(global.display);
        this._updateShowingNotification();

        let [x, y] = 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.

        this._notificationState = State.SHOWING;
        this._bannerBin.remove_all_transitions();
        this._bannerBin.ease({
            opacity: 255,
            duration: ANIMATION_TIME,
            mode: Clutter.AnimationMode.LINEAR,
        });
        this._bannerBin.ease({
            y: 0,
            duration: ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_BACK,
            onComplete: () => {
                this._notificationState = State.SHOWN;
                this._showNotificationCompleted();
                this._updateState();
            },
        });
    }

    _showNotificationCompleted() {
        if (this._notification.urgency != Urgency.CRITICAL)
            this._updateNotificationTimeout(NOTIFICATION_TIMEOUT);
    }

    _updateNotificationTimeout(timeout) {
        if (this._notificationTimeoutId) {
            GLib.source_remove(this._notificationTimeoutId);
            this._notificationTimeoutId = 0;
        }
        if (timeout > 0) {
            this._notificationTimeoutId =
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, timeout,
                    this._notificationTimeout.bind(this));
            GLib.Source.set_name_by_id(this._notificationTimeoutId, '[gnome-shell] this._notificationTimeout');
        }
    }

    _notificationTimeout() {
        let [x, y] = 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();

        this._banner.disconnectObject(this);

        this._resetNotificationLeftTimeout();
        this._bannerBin.remove_all_transitions();

        if (animate) {
            this._notificationState = State.HIDING;
            this._bannerBin.ease({
                opacity: 0,
                duration: ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_BACK,
            });
            this._bannerBin.ease({
                y: -this._bannerBin.height,
                duration: ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_BACK,
                onComplete: () => {
                    this._notificationState = State.HIDDEN;
                    this._hideNotificationCompleted();
                    this._updateState();
                },
            });
        } else {
            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 (!this._notificationRemoved && notification.isTransient)
            notification.destroy(NotificationDestroyedReason.EXPIRED);

        this._pointerInNotification = false;
        this._notificationRemoved = false;
        Meta.enable_unredirect_for_display(global.display);

        this._banner.destroy();
        this._banner = null;
        this.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();
    }
});

var SystemNotificationSource = GObject.registerClass(
class SystemNotificationSource extends Source {
    _init() {
        super._init(_("System Information"), 'dialog-information-symbolic');
    }

    open() {
        this.destroy();
    }
});
(uuay)brightness.js�
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Gio, GObject, 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 = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();
        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._sliderChangedId = this._slider.connect('notify::value',
            this._sliderChanged.bind(this));
        this._slider.accessible_name = _("Brightness");

        const icon = new St.Icon({
            icon_name: 'display-brightness-symbolic',
            style_class: 'popup-menu-icon',
        });
        this._item.add(icon);
        this._item.add_child(this._slider);
        this._item.connect('button-press-event', (actor, event) => {
            return this._slider.startDragging(event);
        });
        this._item.connect('key-press-event', (actor, event) => {
            return this._slider.emit('key-press-event', event);
        });
        this._item.connect('scroll-event', (actor, event) => {
            return this._slider.emit('scroll-event', event);
        });
    }

    _sliderChanged() {
        let percent = this._slider.value * 100;
        this._proxy.Brightness = percent;
    }

    _changeSlider(value) {
        this._slider.block_signal_handler(this._sliderChangedId);
        this._slider.value = value;
        this._slider.unblock_signal_handler(this._sliderChangedId);
    }

    _sync() {
        let visible = this._proxy.Brightness >= 0;
        this._item.visible = visible;
        if (visible)
            this._changeSlider(this._proxy.Brightness / 100.0);
    }
});
(uuay)util.js�w// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported BANNER_MESSAGE_KEY, BANNER_MESSAGE_TEXT_KEY, LOGO_KEY,
            DISABLE_USER_LIST_KEY, fadeInActor, fadeOutActor, cloneAndFadeOutActor */

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

const Batch = imports.gdm.batch;
const OVirt = imports.gdm.oVirt;
const Vmware = imports.gdm.vmware;
const Main = imports.ui.main;
const { loadInterfaceXML } = imports.misc.fileUtils;
const Params = imports.misc.params;
const SmartcardManager = imports.misc.smartcardManager;

const FprintManagerIface = loadInterfaceXML('net.reactivated.Fprint.Manager');
const FprintManagerProxy = Gio.DBusProxy.makeProxyWrapper(FprintManagerIface);
const FprintDeviceIface = loadInterfaceXML('net.reactivated.Fprint.Device');
const FprintDeviceProxy = Gio.DBusProxy.makeProxyWrapper(FprintDeviceIface);

Gio._promisify(Gdm.Client.prototype, 'open_reauthentication_channel');
Gio._promisify(Gdm.Client.prototype, 'get_user_verifier');
Gio._promisify(Gdm.UserVerifierProxy.prototype,
    'call_begin_verification_for_user');
Gio._promisify(Gdm.UserVerifierProxy.prototype, 'call_begin_verification');

var PASSWORD_SERVICE_NAME = 'gdm-password';
var FINGERPRINT_SERVICE_NAME = 'gdm-fingerprint';
var SMARTCARD_SERVICE_NAME = 'gdm-smartcard';
var FADE_ANIMATION_TIME = 160;
var CLONE_FADE_ANIMATION_TIME = 250;

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;
const FINGERPRINT_SERVICE_PROXY_TIMEOUT = 5000;
const FINGERPRINT_ERROR_TIMEOUT_WAIT = 15;

var MessageType = {
    // Keep messages in order by priority
    NONE: 0,
    HINT: 1,
    INFO: 2,
    ERROR: 3,
};

const FingerprintReaderType = {
    NONE: 0,
    PRESS: 1,
    SWIPE: 2,
};

function fadeInActor(actor) {
    if (actor.opacity == 255 && actor.visible)
        return null;

    let hold = new Batch.Hold();
    actor.show();
    let [, naturalHeight] = actor.get_preferred_height(-1);

    actor.opacity = 0;
    actor.set_height(0);
    actor.ease({
        opacity: 255,
        height: naturalHeight,
        duration: FADE_ANIMATION_TIME,
        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        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();
    actor.ease({
        opacity: 0,
        height: 0,
        duration: FADE_ANIMATION_TIME,
        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        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();

    const 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();
    clone.ease({
        opacity: 0,
        duration: CLONE_FADE_ANIMATION_TIME,
        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        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._updateDefaultServiceWithFallback());

        this._fingerprintReaderType = FingerprintReaderType.NONE;
        if (this._settings.get_boolean(FINGERPRINT_AUTHENTICATION_KEY)) {
            const fprintManager = new FprintManagerProxy(Gio.DBus.system,
                'net.reactivated.Fprint',
                '/net/reactivated/Fprint/Manager',
                null,
                null,
                Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES |
                Gio.DBusProxyFlags.DO_NOT_AUTO_START_AT_CONSTRUCTION |
                Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS);

            // Do not wait too much for fprintd to reply, as in case it hangs
            // we should fail early without having the shell to misbehave because
            fprintManager.set_default_timeout(FINGERPRINT_SERVICE_PROXY_TIMEOUT);

            this._updateDefaultService();

            if (!this._defaultService) {
                // Fingerprint is the default one, we must wait for it!
                try {
                    const [devicePath] = fprintManager.GetDefaultDeviceSync();
                    this._fprintManager = fprintManager;

                    const fprintDeviceProxy = new FprintDeviceProxy(Gio.DBus.system,
                        'net.reactivated.Fprint', devicePath, null, null,
                        Gio.DBusProxyFlags.NOT_CONNECT_SIGNALS);
                    this._setFingerprintReaderType(fprintDeviceProxy['scan-type']);
                } catch (e) {
                    logError(e, 'Failed to initialize fprintd service');
                } finally {
                    this._updateDefaultServiceWithFallback();
                }
            } else {
                // Ensure fingerprint service starts, but do not wait for it
                this._updateFingerprintReaderType(fprintManager, null, error => {
                    this._fprintManager = fprintManager;
                    if (error)
                        logError(error, 'Failed to initialize fprintd service');
                });
            }
        } else {
            this._updateDefaultServiceWithFallback();
        }
        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._smartcardManager.connectObject(
            'smartcard-inserted', this._checkForSmartcard.bind(this),
            'smartcard-removed', this._checkForSmartcard.bind(this), this);

        this._messageQueue = [];
        this._messageQueueTimeoutId = 0;
        this.reauthenticating = false;

        this._failCounter = 0;
        this._startedServices = new Set();
        this._unavailableServices = new Set();

        this._credentialManagers = {};
        this._credentialManagers[OVirt.SERVICE_NAME] = OVirt.getOVirtCredentialsManager();
        this._credentialManagers[Vmware.SERVICE_NAME] = Vmware.getVmwareCredentialsManager();

        for (let service in this._credentialManagers) {
            if (this._credentialManagers[service].token) {
                this._onCredentialManagerAuthenticated(this._credentialManagers[service],
                    this._credentialManagers[service].token);
            }

            this._credentialManagers[service].connectObject('user-authenticated',
                this._onCredentialManagerAuthenticated.bind(this), this);
        }
    }

    get hasPendingMessages() {
        return !!this._messageQueue.length;
    }

    get allowedFailures() {
        return this._settings.get_int(ALLOWED_FAILURES_KEY);
    }

    get currentMessage() {
        return this._messageQueue ? this._messageQueue[0] : null;
    }

    begin(userName, hold) {
        this._cancellable = new Gio.Cancellable();
        this._hold = hold;
        this._userName = userName;
        this.reauthenticating = false;

        this._checkForFingerprintReader();

        // If possible, reauthenticate an already running session,
        // so any session specific credentials get updated appropriately
        if (userName)
            this._openReauthenticationChannel(userName);
        else
            this._getUserVerifier();
    }

    cancel() {
        if (this._cancellable)
            this._cancellable.cancel();

        if (this._userVerifier) {
            this._userVerifier.call_cancel_sync(null);
            this.clear();
        }
    }

    _clearUserVerifier() {
        if (this._userVerifier) {
            this._disconnectSignals();
            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();
        this._startedServices.clear();
    }

    destroy() {
        this.cancel();

        this._settings.run_dispose();
        this._settings = null;

        this._smartcardManager.disconnectObject(this);
        this._smartcardManager = null;

        for (let service in this._credentialManagers) {
            let credentialManager = this._credentialManagers[service];
            credentialManager.disconnectObject(this);
            credentialManager = null;
        }
    }

    selectChoice(serviceName, key) {
        this._userVerifierChoiceList.call_select_choice(serviceName, key, this._cancellable, null);
    }

    async answerQuery(serviceName, answer) {
        try {
            await this._handlePendingMessages();
            this._userVerifier.call_answer_query(serviceName, answer, this._cancellable, null);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        }
    }

    _getIntervalForMessage(message) {
        if (!message)
            return 0;

        // We probably could be smarter here
        return message.length * USER_READ_TIME;
    }

    finishMessageQueue() {
        if (!this.hasPendingMessages)
            return;

        this._messageQueue = [];

        this.emit('no-more-messages');
    }

    increaseCurrentMessageTimeout(interval) {
        if (!this._messageQueueTimeoutId && interval > 0)
            this._currentMessageExtraInterval = interval;
    }

    _serviceHasPendingMessages(serviceName) {
        return this._messageQueue.some(m => m.serviceName === serviceName);
    }

    _filterServiceMessages(serviceName, messageType) {
        // This function allows to remove queued messages for the @serviceName
        // whose type has lower priority than @messageType, replacing them
        // with a null message that will lead to clearing the prompt once done.
        if (this._serviceHasPendingMessages(serviceName))
            this._queuePriorityMessage(serviceName, null, messageType);
    }

    _queueMessageTimeout() {
        if (this._messageQueueTimeoutId != 0)
            return;

        const message = this.currentMessage;

        delete this._currentMessageExtraInterval;
        this.emit('show-message', message.serviceName, message.text, message.type);

        this._messageQueueTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
            message.interval + (this._currentMessageExtraInterval | 0), () => {
                this._messageQueueTimeoutId = 0;

                if (this._messageQueue.length > 1) {
                    this._messageQueue.shift();
                    this._queueMessageTimeout();
                } else {
                    this.finishMessageQueue();
                }

                return GLib.SOURCE_REMOVE;
            });
        GLib.Source.set_name_by_id(this._messageQueueTimeoutId, '[gnome-shell] this._queueMessageTimeout');
    }

    _queueMessage(serviceName, message, messageType) {
        let interval = this._getIntervalForMessage(message);

        this._messageQueue.push({ serviceName, text: message, type: messageType, interval });
        this._queueMessageTimeout();
    }

    _queuePriorityMessage(serviceName, message, messageType) {
        const newQueue = this._messageQueue.filter(m => {
            if (m.serviceName !== serviceName || m.type >= messageType)
                return m.text !== message;
            return false;
        });

        if (!newQueue.includes(this.currentMessage))
            this._clearMessageQueue();

        this._messageQueue = newQueue;
        this._queueMessage(serviceName, message, messageType);
    }

    _clearMessageQueue() {
        this.finishMessageQueue();

        if (this._messageQueueTimeoutId != 0) {
            GLib.source_remove(this._messageQueueTimeoutId);
            this._messageQueueTimeoutId = 0;
        }
        this.emit('show-message', null, null, MessageType.NONE);
    }

    _checkForFingerprintReader() {
        if (!this._fprintManager) {
            this._updateDefaultServiceWithFallback();
            return;
        }

        if (this._fingerprintReaderType !== FingerprintReaderType.NONE)
            return;

        this._updateFingerprintReaderType(this._fprintManager, this._cancellable);
    }

    _updateFingerprintReaderType(fprintManager, cancellable, callback) {
        fprintManager.GetDefaultDeviceRemote(
            // Wrappers don't support null cancellable, so let's cheat about it
            cancellable ? cancellable : Gio.DBusCallFlags.NONE,
            (params, error) => {
                if (!error && !params)
                    error = new Error('Unexpected returned parameters');

                if (!error) {
                    const [device] = params;
                    const fprintDeviceProxy = new FprintDeviceProxy(Gio.DBus.system,
                        'net.reactivated.Fprint',
                        device, (_, localError) => {
                            if (!localError) {
                                this._setFingerprintReaderType(fprintDeviceProxy['scan-type']);
                                this._updateDefaultServiceWithFallback();

                                if (this._userVerifier &&
                                    !this._startedServices.has(FINGERPRINT_SERVICE_NAME)) {
                                    if (!this._hold?.isAcquired())
                                        this._hold = new Batch.Hold();
                                    this._maybeStartFingerprintVerification().catch(e => logError(e));
                                }
                            }

                            if (callback)
                                callback(localError);
                        }, cancellable, Gio.DBusProxyFlags.NOT_CONNECT_SIGNALS);
                } else if (callback) {
                    callback(error);
                }
            });
    }

    _setFingerprintReaderType(fprintDeviceType) {
        this._fingerprintReaderType = fprintDeviceType === 'swipe'
            ? FingerprintReaderType.SWIPE
            : FingerprintReaderType.PRESS;
    }

    _onCredentialManagerAuthenticated(credentialManager, _token) {
        this._preemptingService = credentialManager.service;
        this.emit('credential-manager-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.emit('smartcard-status-changed');
        }
    }

    _reportInitError(where, error, serviceName) {
        logError(error, where);
        this._hold.release();

        this._queueMessage(serviceName, _('Authentication error'), MessageType.ERROR);
        this._failCounter++;
        this._verificationFailed(serviceName, false);
    }

    async _openReauthenticationChannel(userName) {
        try {
            this._clearUserVerifier();
            this._userVerifier = await this._client.open_reauthentication_channel(
                userName, this._cancellable);
        } 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
                this._getUserVerifier();
                return;
            }

            this._reportInitError('Failed to open reauthentication channel', e);
            return;
        }

        if (this._client.get_user_verifier_choice_list)
            this._userVerifierChoiceList = this._client.get_user_verifier_choice_list();
        else
            this._userVerifierChoiceList = null;

        this.reauthenticating = true;
        this._connectSignals();
        this._beginVerification();
        this._hold.release();
    }

    async _getUserVerifier() {
        try {
            this._clearUserVerifier();
            this._userVerifier =
                await this._client.get_user_verifier(this._cancellable);
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;
            this._reportInitError('Failed to obtain user verifier', e);
            return;
        }

        if (this._client.get_user_verifier_choice_list)
            this._userVerifierChoiceList = this._client.get_user_verifier_choice_list();
        else
            this._userVerifierChoiceList = null;

        this._connectSignals();
        this._beginVerification();
        this._hold.release();
    }

    _connectSignals() {
        this._disconnectSignals();

        this._userVerifier.connectObject(
            'info', this._onInfo.bind(this),
            'problem', this._onProblem.bind(this),
            'info-query', this._onInfoQuery.bind(this),
            'secret-info-query', this._onSecretInfoQuery.bind(this),
            'conversation-started', this._onConversationStarted.bind(this),
            'conversation-stopped', this._onConversationStopped.bind(this),
            'service-unavailable', this._onServiceUnavailable.bind(this),
            'reset', this._onReset.bind(this),
            'verification-complete', this._onVerificationComplete.bind(this),
            this);

        if (this._userVerifierChoiceList) {
            this._userVerifierChoiceList.connectObject('choice-query',
                this._onChoiceListQuery.bind(this), this);
        }
    }

    _disconnectSignals() {
        this._userVerifier?.disconnectObject(this);
        this._userVerifierChoiceList?.disconnectObject(this);
    }

    _getForegroundService() {
        if (this._preemptingService)
            return this._preemptingService;

        return this._defaultService;
    }

    serviceIsForeground(serviceName) {
        return serviceName == this._getForegroundService();
    }

    serviceIsDefault(serviceName) {
        return serviceName == this._defaultService;
    }

    serviceIsFingerprint(serviceName) {
        return this._fingerprintReaderType !== FingerprintReaderType.NONE &&
            serviceName === FINGERPRINT_SERVICE_NAME;
    }

    _updateDefaultService() {
        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._fingerprintReaderType !== FingerprintReaderType.NONE)
            this._defaultService = FINGERPRINT_SERVICE_NAME;
    }

    _updateDefaultServiceWithFallback() {
        this._updateDefaultService();

        if (!this._defaultService) {
            log("no authentication service is enabled, using password authentication");
            this._defaultService = PASSWORD_SERVICE_NAME;
        }
    }

    async _startService(serviceName) {
        this._hold.acquire();
        try {
            if (this._userName) {
                await this._userVerifier.call_begin_verification_for_user(
                    serviceName, this._userName, this._cancellable);
            } else {
                await this._userVerifier.call_begin_verification(
                    serviceName, this._cancellable);
            }
        } catch (e) {
            if (e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                return;
            if (!this.serviceIsForeground(serviceName)) {
                logError(e,
                    `Failed to start ${serviceName} for ${this._userName}`);
                this._hold.release();
                return;
            }
            this._reportInitError(
                this._userName
                    ? `Failed to start ${serviceName} verification for user`
                    : `Failed to start ${serviceName} verification`,
                e, serviceName);
            return;
        }
        this._hold.release();
    }

    _beginVerification() {
        this._startService(this._getForegroundService());
        this._maybeStartFingerprintVerification();
    }

    async _maybeStartFingerprintVerification() {
        if (this._userName &&
            this._fingerprintReaderType !== FingerprintReaderType.NONE &&
            !this.serviceIsForeground(FINGERPRINT_SERVICE_NAME))
            await 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(serviceName, info, MessageType.INFO);
        } else if (this.serviceIsFingerprint(serviceName)) {
            // 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.
            if (this._fingerprintReaderType === FingerprintReaderType.SWIPE) {
                // Translators: this message is shown below the password entry field
                // to indicate the user can swipe their finger on the fingerprint reader
                this._queueMessage(serviceName, _('(or swipe finger across reader)'),
                    MessageType.HINT);
            } else {
                // Translators: this message is shown below the password entry field
                // to indicate the user can place their finger on the fingerprint reader instead
                this._queueMessage(serviceName, _('(or place finger on reader)'),
                    MessageType.HINT);
            }
        }
    }

    _onProblem(client, serviceName, problem) {
        const isFingerprint = this.serviceIsFingerprint(serviceName);

        if (!this.serviceIsForeground(serviceName) && !isFingerprint)
            return;

        this._queuePriorityMessage(serviceName, problem, MessageType.ERROR);

        if (isFingerprint) {
            // pam_fprintd allows the user to retry multiple (maybe even infinite!
            // times before failing the authentication conversation.
            // We don't want this behavior to bypass the max-tries setting the user has set,
            // so we count the problem messages to know how many times the user has failed.
            // Once we hit the max number of failures we allow, it's time to failure the
            // conversation from our side. We can't do that right away, however, because
            // we may drop pending messages coming from pam_fprintd. In order to make sure
            // the user sees everything, we queue the failure up to get handled in the
            // near future, after we've finished up the current round of messages.
            this._failCounter++;

            if (!this._canRetry()) {
                if (this._fingerprintFailedId)
                    GLib.source_remove(this._fingerprintFailedId);

                const cancellable = this._cancellable;
                this._fingerprintFailedId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                    FINGERPRINT_ERROR_TIMEOUT_WAIT, () => {
                        this._fingerprintFailedId = 0;
                        if (!cancellable.is_cancelled())
                            this._verificationFailed(serviceName, false);
                        return GLib.SOURCE_REMOVE;
                    });
            }
        }
    }

    _onInfoQuery(client, serviceName, question) {
        if (!this.serviceIsForeground(serviceName))
            return;

        this.emit('ask-question', serviceName, question, false);
    }

    _onSecretInfoQuery(client, serviceName, secretQuestion) {
        if (!this.serviceIsForeground(serviceName))
            return;

        let token = null;
        if (this._credentialManagers[serviceName])
            token = this._credentialManagers[serviceName].token;

        if (token) {
            this.answerQuery(serviceName, token);
            return;
        }

        this.emit('ask-question', serviceName, secretQuestion, true);
    }

    _onReset() {
        // Clear previous attempts to authenticate
        this._failCounter = 0;
        this._startedServices.clear();
        this._unavailableServices.clear();
        this._updateDefaultServiceWithFallback();

        this.emit('reset');
    }

    _onVerificationComplete() {
        this.emit('verification-complete');
    }

    _cancelAndReset() {
        this.cancel();
        this._onReset();
    }

    _retry(serviceName) {
        this._hold = new Batch.Hold();
        this._connectSignals();
        this._startService(serviceName);
    }

    _canRetry() {
        return this._userName &&
            (this._reauthOnly || this._failCounter < this.allowedFailures);
    }

    async _verificationFailed(serviceName, shouldRetry) {
        if (serviceName === FINGERPRINT_SERVICE_NAME) {
            if (this._fingerprintFailedId)
                GLib.source_remove(this._fingerprintFailedId);
        }

        // 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._filterServiceMessages(serviceName, MessageType.ERROR);

        const doneTrying = !shouldRetry || !this._canRetry();

        this.emit('verification-failed', serviceName, !doneTrying);
        try {
            if (doneTrying) {
                this._disconnectSignals();
                await this._handlePendingMessages();
                this._cancelAndReset();
            } else {
                await this._handlePendingMessages();
                this._retry(serviceName);
            }
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                logError(e);
        }
    }

    _handlePendingMessages() {
        if (!this.hasPendingMessage)
            return Promise.resolve();

        const cancellable = this._cancellable;
        return new Promise((resolve, reject) => {
            let signalId = this.connect('no-more-messages', () => {
                this.disconnect(signalId);
                if (cancellable.is_cancelled())
                    reject(new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED, 'Operation was cancelled'));
                else
                    resolve();
            });
        });
    }

    _onServiceUnavailable(_client, serviceName, errorMessage) {
        this._unavailableServices.add(serviceName);

        if (!errorMessage)
            return;

        if (this.serviceIsForeground(serviceName) || this.serviceIsFingerprint(serviceName))
            this._queueMessage(serviceName, errorMessage, MessageType.ERROR);
    }

    _onConversationStarted(client, serviceName) {
        this._startedServices.add(serviceName);
    }

    _onConversationStopped(client, serviceName) {
        // If the login failed with the preauthenticated oVirt credentials
        // then discard the credentials and revert to default authentication
        // mechanism.
        let foregroundService = Object.keys(this._credentialManagers).find(service =>
            this.serviceIsForeground(service));
        if (foregroundService) {
            this._credentialManagers[foregroundService].token = null;
            this._preemptingService = null;
            this._verificationFailed(serviceName, false);
            return;
        }

        this._filterServiceMessages(serviceName, MessageType.ERROR);

        if (this._unavailableServices.has(serviceName))
            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._failCounter++;

        this._verificationFailed(serviceName, true);
    }
};
Signals.addSignalMethods(ShellUserVerifier.prototype);
(uuay)util.jshO// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported findUrls, spawn, spawnCommandLine, spawnApp, trySpawnCommandLine,
            formatTime, formatTimeSpan, createTimeLabel, insertSorted,
            ensureActorVisibleInScrollView, wiggle, lerp, GNOMEversionCompare,
            DBusSenderChecker, Highlighter */

const { Clutter, Gio, GLib, Shell, St, GnomeDesktop } = imports.gi;
const Gettext = imports.gettext;

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

var SCROLL_TIME = 100;

const WIGGLE_OFFSET = 6;
const WIGGLE_DURATION = 65;
const N_WIGGLES = 3;

// http://daringfireball.net/2010/07/improved_regex_for_matching_urls
const _balancedParens = '\\([^\\s()<>]+\\)';
const _leadingJunk = '[\\s`(\\[{\'\\"<\u00AB\u201C\u2018]';
const _notTrailingJunk = '[^\\s`!()\\[\\]{};:\'\\".,<>?\u00AB\u00BB\u200E\u200F\u201C\u201D\u2018\u2019\u202A\u202C]';

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:
// @commandLine: a command line
//
// Runs @commandLine in the background, handling any errors that
// occur when trying to parse or start the program.
function spawnCommandLine(commandLine) {
    try {
        let [success_, argv] = GLib.shell_parse_argv(commandLine);
        trySpawn(argv);
    } catch (err) {
        _handleSpawnError(commandLine, 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,
            () => {
                try {
                    global.context.restore_rlimit_nofile();
                } catch (err) {
                }
            }
        );
    } 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 });
        } else {
            throw err;
        }
    }

    // Async call, we don't need the reply though
    GnomeDesktop.start_systemd_scope(argv[0], pid, null, null, null, () => {});

    // 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:
// @commandLine: a command line
//
// Runs @commandLine in the background. If launching @commandLine
// fails, this will throw an error.
function trySpawnCommandLine(commandLine) {
    let success_, argv;

    try {
        [success_, argv] = GLib.shell_parse_argv(commandLine);
    } 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,
        ampm: true,
    });

    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) // eslint-disable-line no-lonely-if
            /* 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");
    }

    // Time in short 12h format, without the equivalent of "AM" or "PM"; used
    // when it is clear from the context
    if (!params.ampm)
        format = format.replace(/\s*%p/g, '');

    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) });
    _desktopSettings.connectObject(
        'changed::clock-format', () => (label.text = formatTime(date, params)),
        label);
    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 ||= (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;
}

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.fade_margins.top;

    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");

        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;

    adjustment.ease(value, {
        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        duration: SCROLL_TIME,
    });
}

function wiggle(actor, params) {
    if (!St.Settings.get().enable_animations)
        return;

    params = Params.parse(params, {
        offset: WIGGLE_OFFSET,
        duration: WIGGLE_DURATION,
        wiggleCount: N_WIGGLES,
    });
    actor.translation_x = 0;

    // Accelerate before wiggling
    actor.ease({
        translation_x: -params.offset,
        duration: params.duration,
        mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        onComplete: () => {
            // Wiggle
            actor.ease({
                translation_x: params.offset,
                duration: params.duration,
                mode: Clutter.AnimationMode.LINEAR,
                repeatCount: params.wiggleCount,
                autoReverse: true,
                onComplete: () => {
                    // Decelerate and return to the original position
                    actor.ease({
                        translation_x: 0,
                        duration: params.duration,
                        mode: Clutter.AnimationMode.EASE_IN_QUAD,
                    });
                },
            });
        },
    });
}

function lerp(start, end, progress) {
    return start + progress * (end - start);
}

// _GNOMEversionToNumber:
// @version: a GNOME version element
//
// Like Number() but returns sortable values for special-cases
// 'alpha' and 'beta'. Returns NaN for unhandled 'versions'.
function _GNOMEversionToNumber(version) {
    let ret = Number(version);
    if (!isNaN(ret))
        return ret;
    if (version === 'alpha')
        return -2;
    if (version === 'beta')
        return -1;
    return ret;
}

// GNOMEversionCompare:
// @version1: a string containing a GNOME version
// @version2: a string containing another GNOME version
//
// Returns an integer less than, equal to, or greater than
// zero, if version1 is older, equal or newer than version2
function GNOMEversionCompare(version1, version2) {
    const v1Array = version1.split('.');
    const v2Array = version2.split('.');

    for (let i = 0; i < Math.max(v1Array.length, v2Array.length); i++) {
        let elemV1 = _GNOMEversionToNumber(v1Array[i] || '0');
        let elemV2 = _GNOMEversionToNumber(v2Array[i] || '0');
        if (elemV1 < elemV2)
            return -1;
        if (elemV1 > elemV2)
            return 1;
    }

    return 0;
}

var DBusSenderChecker = class {
    /**
     * @param {string[]} allowList - list of allowed well-known names
     */
    constructor(allowList) {
        this._allowlistMap = new Map();

        this._uninitializedNames = new Set(allowList);
        this._initializedPromise = new Promise(resolve => {
            this._resolveInitialized = resolve;
        });

        this._watchList = allowList.map(name => {
            return Gio.DBus.watch_name(Gio.BusType.SESSION,
                name,
                Gio.BusNameWatcherFlags.NONE,
                (conn_, name_, owner) => {
                    this._allowlistMap.set(name, owner);
                    this._checkAndResolveInitialized(name);
                },
                () => {
                    this._allowlistMap.delete(name);
                    this._checkAndResolveInitialized(name);
                });
        });
    }

    /**
     * @param {string} name - bus name for which the watcher got initialized
     */
    _checkAndResolveInitialized(name) {
        if (this._uninitializedNames.delete(name) &&
            this._uninitializedNames.size === 0)
            this._resolveInitialized();
    }

    /**
     * @async
     * @param {string} sender - the bus name that invoked the checked method
     * @returns {bool}
     */
    async _isSenderAllowed(sender) {
        await this._initializedPromise;
        return [...this._allowlistMap.values()].includes(sender);
    }

    /**
     * Check whether the bus name that invoked @invocation maps
     * to an entry in the allow list.
     *
     * @async
     * @throws
     * @param {Gio.DBusMethodInvocation} invocation - the invocation
     * @returns {void}
     */
    async checkInvocation(invocation) {
        if (global.context.unsafe_mode)
            return;

        if (await this._isSenderAllowed(invocation.get_sender()))
            return;

        throw new GLib.Error(Gio.DBusError,
            Gio.DBusError.ACCESS_DENIED,
            `${invocation.get_method_name()} is not allowed`);
    }

    /**
     * @returns {void}
     */
    destroy() {
        for (const id in this._watchList)
            Gio.DBus.unwatch_name(id);
        this._watchList = [];
    }
};

/* @class Highlighter Highlight given terms in text using markup. */
var Highlighter = class {
    /**
     * @param {?string[]} terms - list of terms to highlight
     */
    constructor(terms) {
        if (!terms)
            return;

        const escapedTerms = terms
            .map(term => Shell.util_regex_escape(term))
            .filter(term => term.length > 0);

        if (escapedTerms.length === 0)
            return;

        this._highlightRegex = new RegExp(
            `(${escapedTerms.join('|')})`, 'gi');
    }

    /**
     * Highlight all occurences of the terms defined for this
     * highlighter in the provided text using markup.
     *
     * @param {string} text - text to highlight the defined terms in
     * @returns {string}
     */
    highlight(text) {
        if (!this._highlightRegex)
            return GLib.markup_escape_text(text, -1);

        let escaped = [];
        let lastMatchEnd = 0;
        let match;
        while ((match = this._highlightRegex.exec(text))) {
            if (match.index > lastMatchEnd) {
                let unmatched = GLib.markup_escape_text(
                    text.slice(lastMatchEnd, match.index), -1);
                escaped.push(unmatched);
            }
            let matched = GLib.markup_escape_text(match[0], -1);
            escaped.push(`<b>${matched}</b>`);
            lastMatchEnd = match.index + match[0].length;
        }
        let unmatched = GLib.markup_escape_text(
            text.slice(lastMatchEnd), -1);
        escaped.push(unmatched);

        return escaped.join('');
    }
};
(uuay)inhibitShortcutsDialog.jsg/* exported InhibitShortcutsDialog */
const { Clutter, Gio, GObject, Gtk, Meta, Pango, Shell, St } = 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_ALLOWLIST = ['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;

        this._permStore.SetPermissionRemote(APP_PERMISSIONS_TABLE,
            true,
            APP_PERMISSIONS_ID,
            this._app.get_id(),
            [grant],
            (result, error) => {
                if (error != null)
                    log(error.message);
            });
    }

    _buildLayout() {
        const name = this._app?.get_name() ?? this._window.title;

        let content = new Dialog.MessageDialogContent({
            title: _('Allow inhibiting shortcuts'),
            description: name
                /* Translators: %s is an application name like "Settings" */
                ? _('The application %s wants to inhibit shortcuts').format(name)
                : _('An application wants to inhibit shortcuts'),
        });

        let restoreAccel = this._getRestoreAccel();
        if (restoreAccel) {
            let restoreLabel = new St.Label({
                /* Translators: %s is a keyboard shortcut like "Super+x" */
                text: _('You can restore shortcuts by pressing %s.').format(restoreAccel),
                style_class: 'message-dialog-description',
            });
            restoreLabel.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
            restoreLabel.clutter_text.line_wrap = true;
            content.add_child(restoreLabel);
        }

        this._dialog.contentLayout.add_child(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_ALLOWLIST.includes(this._app.get_id())) {
            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, err) => {
                    if (err) {
                        this._dialog.open();
                        log(err.message);
                        return;
                    }

                    let [permissions] = res;
                    if (permissions[appId] === undefined) // Not found
                        this._dialog.open();
                    else if (permissions[appId][0] === GRANTED)
                        this._emitResponse(DialogResponse.ALLOW);
                    else
                        this._emitResponse(DialogResponse.DENY);
                });
        });
    }

    vfunc_hide() {
        this._dialog.close();
    }
});
(uuay)extensionSystem.js�]// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init connect disconnect */

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

const Desktop = imports.misc.desktop;
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 DISABLED_EXTENSIONS_KEY = 'disabled-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._initialized = false;
        this._updateNotified = false;

        this._extensions = new Map();
        this._unloadedExtensions = new Map();
        this._enabledExtensions = [];
        this._extensionOrder = [];
        this._checkVersion = false;

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

    init() {
        // The following file should exist for a period of time when extensions
        // are enabled after start. If it exists, then the systemd unit will
        // disable extensions should gnome-shell crash.
        // Should the file already exist from a previous login, then this is OK.
        let disableFilename = GLib.build_filenamev([GLib.get_user_runtime_dir(), 'gnome-shell-disable-extensions']);
        let disableFile = Gio.File.new_for_path(disableFilename);
        try {
            disableFile.create(Gio.FileCreateFlags.REPLACE_DESTINATION, null);
        } catch (e) {
            log(`Failed to create file ${disableFilename}: ${e.message}`);
        }

        GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 60, () => {
            disableFile.delete(null);
            return GLib.SOURCE_REMOVE;
        });

        this._installExtensionUpdates();
        this._sessionUpdated();

        GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, UPDATE_CHECK_TIMEOUT, () => {
            ExtensionDownloader.checkForUpdates();
            return GLib.SOURCE_CONTINUE;
        });
        ExtensionDownloader.checkForUpdates();
    }

    get updatesSupported() {
        const appSys = Shell.AppSystem.get_default();
        return (appSys.lookup_app('org.gnome.Extensions.desktop') !== null) ||
               (appSys.lookup_app('com.mattjakeman.ExtensionManager.desktop') !== null);
    }

    lookup(uuid) {
        return this._extensions.get(uuid);
    }

    getUuids() {
        return [...this._extensions.keys()];
    }

    _extensionSupportsSessionMode(uuid) {
        const 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;
    }

    _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 otherUuid = orderReversed[i];
            try {
                this.lookup(otherUuid).stateObj.disable();
            } catch (e) {
                this.logExtensionError(otherUuid, e);
            }
        }

        try {
            extension.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;
        }

        for (let i = 0; i < order.length; i++) {
            let otherUuid = order[i];
            try {
                this.lookup(otherUuid).stateObj.enable();
            } catch (e) {
                this.logExtensionError(otherUuid, 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._extensionSupportsSessionMode(uuid))
            return;

        let extension = this.lookup(uuid);
        if (!extension)
            return;

        if (extension.state == ExtensionState.INITIALIZED)
            this._callExtensionInit(uuid);

        if (extension.state != ExtensionState.DISABLED)
            return;

        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
                this.logExtensionError(uuid, e);
                return;
            }
        }

        try {
            extension.stateObj.enable();
            extension.state = ExtensionState.ENABLED;
            this._extensionOrder.push(uuid);
            this.emit('extension-state-changed', extension);
        } catch (e) {
            if (extension.stylesheet) {
                theme.unload_stylesheet(extension.stylesheet);
                delete extension.stylesheet;
            }
            this.logExtensionError(uuid, e);
        }
    }

    enableExtension(uuid) {
        if (!this._extensions.has(uuid))
            return false;

        let enabledExtensions = global.settings.get_strv(ENABLED_EXTENSIONS_KEY);
        let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);

        if (disabledExtensions.includes(uuid)) {
            disabledExtensions = disabledExtensions.filter(item => item !== uuid);
            global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
        }

        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);
        let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);

        if (enabledExtensions.includes(uuid)) {
            enabledExtensions = enabledExtensions.filter(item => item !== uuid);
            global.settings.set_strv(ENABLED_EXTENSIONS_KEY, enabledExtensions);
        }

        if (!disabledExtensions.includes(uuid)) {
            disabledExtensions.push(uuid);
            global.settings.set_strv(DISABLED_EXTENSIONS_KEY, disabledExtensions);
        }

        return true;
    }

    openExtensionPrefs(uuid, parentWindow, options) {
        const extension = this.lookup(uuid);
        if (!extension || !extension.hasPrefs)
            return false;

        Gio.DBus.session.call(
            'org.gnome.Shell.Extensions',
            '/org/gnome/Shell/Extensions',
            'org.gnome.Shell.Extensions',
            'OpenExtensionPrefs',
            new GLib.Variant('(ssa{sv})', [uuid, parentWindow, options]),
            null,
            Gio.DBusCallFlags.NONE,
            -1,
            null);
        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.showNotification(notification);
        }
    }

    logExtensionError(uuid, error) {
        let extension = this.lookup(uuid);
        if (!extension)
            return;

        const message = error instanceof Error
            ? error.message : error.toString();

        extension.error = message;
        extension.state = ExtensionState.ERROR;
        if (!extension.errors)
            extension.errors = [];
        extension.errors.push(message);

        logError(error, `Extension ${uuid}`);
        this._updateCanChange(extension);
        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);
            metadataContents = new TextDecoder().decode(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}`);
        }

        const requiredProperties = [{
            prop: 'uuid',
            typeName: 'string',
        }, {
            prop: 'name',
            typeName: 'string',
        }, {
            prop: 'description',
            typeName: 'string',
        }, {
            prop: 'shell-version',
            typeName: 'string array',
            typeCheck: v => Array.isArray(v) && v.length > 0 && v.every(e => typeof e === 'string'),
        }];
        for (let i = 0; i < requiredProperties.length; i++) {
            const {
                prop, typeName, typeCheck = v => typeof v === typeName,
            } = requiredProperties[i];

            if (!meta[prop])
                throw new Error(`missing "${prop}" property in metadata.json`);
            if (!typeCheck(meta[prop]))
                throw new Error(`property "${prop}" is not of type ${typeName}`);
        }

        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;
    }

    _canLoad(extension) {
        if (!this._unloadedExtensions.has(extension.uuid))
            return true;

        const version = this._unloadedExtensions.get(extension.uuid);
        return extension.metadata.version === version;
    }

    loadExtension(extension) {
        // Default to error, we set success as the last step
        extension.state = ExtensionState.ERROR;

        if (this._checkVersion && ExtensionUtils.isOutOfDate(extension)) {
            extension.state = ExtensionState.OUT_OF_DATE;
        } else if (!this._canLoad(extension)) {
            this.logExtensionError(extension.uuid, new Error(
                'A different version was loaded previously. You need to log out for changes to take effect.'));
        } else {
            let enabled = this._enabledExtensions.includes(extension.uuid) &&
                          this._extensionSupportsSessionMode(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._unloadedExtensions.delete(extension.uuid);
        }

        this._updateCanChange(extension);
        this.emit('extension-state-changed', extension);
    }

    unloadExtension(extension) {
        const { uuid, type } = 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(uuid);

        extension.state = ExtensionState.UNINSTALLED;
        this.emit('extension-state-changed', extension);

        // If we did install an importer, it is now cached and it's
        // impossible to load a different version
        if (type === ExtensionType.PER_USER && extension.imports)
            this._unloadedExtensions.set(uuid, extension.metadata.version);

        this._extensions.delete(uuid);
        return true;
    }

    reloadExtension(oldExtension) {
        // Grab the things we'll need to pass to createExtensionObject
        // to reload it.
        let { uuid, dir, 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);
    }

    isModeExtension(uuid) {
        return this._getModeExtensions().indexOf(uuid) !== -1;
    }

    _callExtensionInit(uuid) {
        if (!this._extensionSupportsSessionMode(uuid))
            return false;

        let extension = this.lookup(uuid);
        if (!extension)
            throw new Error("Extension was not properly created. Call createExtensionObject first");

        let dir = extension.dir;
        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);

        let changeKey = isMode
            ? DISABLE_USER_EXTENSIONS_KEY
            : ENABLED_EXTENSIONS_KEY;

        extension.canChange =
            !hasError &&
            global.settings.is_writable(changeKey) &&
            (isMode || !modeOnly);
    }

    _getEnabledExtensions() {
        let extensions = this._getModeExtensions();

        if (!global.settings.get_boolean(DISABLE_USER_EXTENSIONS_KEY))
            extensions = extensions.concat(global.settings.get_strv(ENABLED_EXTENSIONS_KEY));

        // filter out 'disabled-extensions' which takes precedence
        let disabledExtensions = global.settings.get_strv(DISABLED_EXTENSIONS_KEY);
        return extensions.filter(item => !disabledExtensions.includes(item));
    }

    _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.
        this._extensionOrder
            .filter(uuid => !newEnabledExtensions.includes(uuid) ||
                            !this._extensionSupportsSessionMode(uuid))
            .reverse().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() {
        const checkVersion = !global.settings.get_boolean(EXTENSION_DISABLE_VERSION_CHECK_KEY);
        if (checkVersion === this._checkVersion)
            return;

        this._checkVersion = checkVersion;

        // Disabling extensions modifies the order array, so use a copy
        let extensionOrder = this._extensionOrder.slice();

        // Disable enabled extensions in the reverse order first to avoid
        // the "rebasing" done in _callExtensionDisable...
        extensionOrder.slice().reverse().forEach(uuid => {
            this._callExtensionDisable(uuid);
        });

        // ...and then reload and enable extensions in the correct order again.
        [...this._extensions.values()].sort((a, b) => {
            return extensionOrder.indexOf(a.uuid) - extensionOrder.indexOf(b.uuid);
        }).forEach(extension => this.reloadExtension(extension));
    }

    _installExtensionUpdates() {
        if (!this.updatesSupported)
            return;

        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 ${uuid}`);
            } finally {
                FileUtils.recursivelyDeleteDir(dir, true);
            }
        });
    }

    _loadExtensions() {
        global.settings.connect(`changed::${ENABLED_EXTENSIONS_KEY}`,
            this._onEnabledExtensionsChanged.bind(this));
        global.settings.connect(`changed::${DISABLED_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));
        global.settings.connect(`writable-changed::${DISABLED_EXTENSIONS_KEY}`,
            this._onSettingsWritableChanged.bind(this));

        this._onVersionValidationChanged();

        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;
            if (Desktop.is("ubuntu") && this.isModeExtension(uuid) && type === ExtensionType.PER_USER) {
                log(`Found user extension ${uuid}, but not loading from ${dir.get_path()} directory as part of session mode.`);
                return;
            }
            try {
                extension = this.createExtensionObject(uuid, dir, type);
            } catch (e) {
                logError(e, `Could not load extension ${uuid}`);
                return;
            }
            this.loadExtension(extension);
        });
    }

    _enableAllExtensions() {
        if (!this._initialized) {
            this._loadExtensions();
            this._initialized = true;
        } else {
            this._enabledExtensions.forEach(uuid => {
                this._callExtensionEnable(uuid);
            });
        }
    }

    _disableAllExtensions() {
        if (this._initialized) {
            this._extensionOrder.slice().reverse().forEach(uuid => {
                this._callExtensionDisable(uuid);
            });
        }
    }

    _sessionUpdated() {
        // Take care of added or removed sessionMode extensions
        this._onEnabledExtensionsChanged();
        this._enableAllExtensions();
    }
};
Signals.addSignalMethods(ExtensionManager.prototype);

const ExtensionUpdateSource = GObject.registerClass(
class ExtensionUpdateSource extends MessageTray.Source {
    _init() {
        let appSys = Shell.AppSystem.get_default();
        this._app = appSys.lookup_app('org.gnome.Extensions.desktop');
        if (!this._app)
            this._app = appSys.lookup_app('com.mattjakeman.ExtensionManager.desktop');

        super._init(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)inputMethod.js{'// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported InputMethod */
const { Clutter, GLib, Gio, GObject, IBus } = imports.gi;

const Keyboard = imports.ui.status.keyboard;

Gio._promisify(IBus.Bus.prototype,
    'create_input_context_async', 'create_input_context_async_finish');

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 = IBus.Capabilite.PREEDIT_TEXT | IBus.Capabilite.FOCUS | IBus.Capabilite.SURROUNDING_TEXT;

        if (this._context)
            this._context.set_capabilities(caps);
    }

    _onSourceChanged() {
        this._currentSource = this._inputSourceManager.currentSource;
    }

    async _onConnected() {
        this._cancellable = new Gio.Cancellable();
        try {
            this._context = await this._ibus.create_input_context_async(
                'gnome-shell', -1, this._cancellable);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
                logError(e);
                this._clear();
            }
            return;
        }

        this._context.set_client_commit_preedit(true);
        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-with-mode', 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._context.connect('destroy', this._clear.bind(this));

        this._updateCapabilities();
    }

    _clear() {
        if (this._cancellable) {
            this._cancellable.cancel();
            this._cancellable = null;
        }

        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, offset, nchars) {
        try {
            this.delete_surrounding(offset, nchars);
        } catch (e) {
            // We may get out of bounds for negative offset on older mutter
            this.delete_surrounding(0, nchars + offset);
        }
    }

    _onUpdatePreeditText(_context, text, pos, visible, mode) {
        if (text == null)
            return;

        let preedit = text.get_text();
        if (preedit === '')
            preedit = null;

        if (visible)
            this.set_preedit_text(preedit, pos, mode);
        else if (this._preeditVisible)
            this.set_preedit_text(null, pos, mode);

        this._preeditStr = preedit;
        this._preeditPos = pos;
        this._preeditVisible = visible;
        this._preeditCommitMode = mode;
    }

    _onShowPreeditText() {
        this._preeditVisible = true;
        this.set_preedit_text(this._preeditStr, this._preeditPos, this._preeditCommitMode);
    }

    _onHidePreeditText() {
        this.set_preedit_text(null, this._preeditPos, this._preeditCommitMode);
        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._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();

        if (this._preeditStr && this._preeditVisible) {
            // Unset any preedit text
            this.set_preedit_text(null, 0, this._preeditCommitMode);
            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();
        }

        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;
        else if (purpose === Clutter.InputContentPurpose.TERMINAL &&
                 IBus.InputPurpose.TERMINAL)
            ibusPurpose = IBus.InputPurpose.TERMINAL;

        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, this._cancellable,
            (context, res) => {
                if (context != this._context)
                    return;

                try {
                    let retval = context.process_key_event_async_finish(res);
                    this.notify_key_event(event, retval);
                } catch (e) {
                    if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
                        log(`Error processing key on IM: ${e.message}`);
                }
            });
        return true;
    }
});
(uuay)endSessionDialog.js7p// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported init, EndSessionDialog */
/*
 * 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 {
    AccountsService, Clutter, Gio, GLib, GObject,
    Pango, Polkit, Shell, St, UPowerGlib: UPower,
} = imports.gi;

const CheckBox = imports.ui.checkBox;
const Dialog = imports.ui.dialog;
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;

const _ITEM_ICON_SIZE = 64;

const LOW_BATTERY_THRESHOLD = 30;

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'),
    }],
    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: 'ConfirmedShutdown',
        label: C_('button', 'Power Off'),
    }],
    iconName: 'system-shutdown-symbolic',
    showOtherSessions: true,
};

const restartDialogContent = {
    subject: C_("title", "Restart"),
    subjectWithUpdates: C_('title', 'Install Updates & Restart'),
    description(seconds) {
        return ngettext(
            'The system will restart automatically in %d second.',
            'The system will restart automatically in %d seconds.',
            seconds).format(seconds);
    },
    checkBoxText: C_('checkbox', 'Install pending software updates'),
    showBatteryWarning: true,
    confirmButtons: [{
        signal: 'ConfirmedReboot',
        label: C_('button', 'Restart'),
    }],
    iconName: 'view-refresh-symbolic',
    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',
    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',
    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.Device');
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: ${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 _setCheckBoxLabel(checkBox, text) {
    let label = checkBox.getLabelActor();

    if (text) {
        label.set_text(text);
        checkBox.show();
    } else {
        label.set_text('');
        checkBox.hide();
    }
}

function init() {
    // This always returns the same singleton object
    // By instantiating it initially, we register the
    // bus object, etc.
    new EndSessionDialog();
}

var EndSessionDialog = GObject.registerClass(
class EndSessionDialog extends ModalDialog.ModalDialog {
    _init() {
        super._init({
            styleClass: 'end-session-dialog',
            destroyOnClose: false,
        });

        this._loginManager = LoginManager.getLoginManager();
        this._loginManager.canRebootToBootLoaderMenu(
            (canRebootToBootLoaderMenu, unusedNeedsAuth) => {
                this._canRebootToBootLoaderMenu = canRebootToBootLoaderMenu;
            });

        this._userManager = AccountsService.UserManager.get_default();
        this._user = this._userManager.get_user(GLib.get_user_name());
        this._updatesPermission = null;

        this._pkOfflineProxy = new PkOfflineProxy(Gio.DBus.system,
                                                  'org.freedesktop.PackageKit',
                                                  '/org/freedesktop/PackageKit',
                                                  this._onPkOfflineProxyCreated.bind(this));

        this._powerProxy = new UPowerProxy(Gio.DBus.system,
                                           'org.freedesktop.UPower',
                                           '/org/freedesktop/UPower/devices/DisplayDevice',
                                           (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._capturedEventId = 0;
        this._rebootButton = null;
        this._rebootButtonAlt = null;

        this.connect('opened',
                     this._onOpened.bind(this));

        this._user.connectObject(
            'notify::is-loaded', this._sync.bind(this),
            'changed', this._sync.bind(this), this);

        this._messageDialogContent = new Dialog.MessageDialogContent();

        this._checkBox = new CheckBox.CheckBox();
        this._checkBox.connect('clicked', this._sync.bind(this));
        this._messageDialogContent.add_child(this._checkBox);

        this._batteryWarning = new St.Label({
            style_class: 'end-session-dialog-battery-warning',
            text: _('Low battery power: please plug in before installing updates.'),
        });
        this._batteryWarning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._batteryWarning.clutter_text.line_wrap = true;
        this._messageDialogContent.add_child(this._batteryWarning);

        this.contentLayout.add_child(this._messageDialogContent);

        this._applicationSection = new Dialog.ListSection({
            title: _('Some applications are busy or have unsaved work'),
        });
        this.contentLayout.add_child(this._applicationSection);

        this._sessionSection = new Dialog.ListSection({
            title: _('Other users are logged in'),
        });
        this.contentLayout.add_child(this._sessionSection);

        this._dbusImpl = Gio.DBusExportedObject.wrapJSObject(EndSessionDialogIface, this);
        this._dbusImpl.export(Gio.DBus.session, '/org/gnome/SessionManager/EndSessionDialog');
    }

    async _onPkOfflineProxyCreated(proxy, error) {
        if (error) {
            log(error.message);
            return;
        }

        // Creating a D-Bus proxy won't propagate SERVICE_UNKNOWN or NAME_HAS_NO_OWNER
        // errors if PackageKit is not available, but the GIO implementation will make
        // sure in that case that the proxy's g-name-owner is set to null, so check that.
        if (this._pkOfflineProxy.g_name_owner === null) {
            this._pkOfflineProxy = null;
            return;
        }

        // It only makes sense to check for this permission if PackageKit is available.
        try {
            this._updatesPermission = await Polkit.Permission.new(
                'org.freedesktop.packagekit.trigger-offline-update', null, null);
        } catch (e) {
            log(`No permission to trigger offline updates: ${e}`);
        }
    }

    _isDischargingBattery() {
        return this._powerProxy.IsPresent &&
            this._powerProxy.State !== UPower.DeviceState.CHARGING &&
            this._powerProxy.State !== UPower.DeviceState.FULLY_CHARGED;
    }

    _isBatteryLow() {
        return this._isDischargingBattery() && this._powerProxy.Percentage < LOW_BATTERY_THRESHOLD;
    }

    _shouldShowLowBatteryWarning(dialogContent) {
        if (!dialogContent.showBatteryWarning)
            return false;

        if (!this._isBatteryLow())
            return false;

        if (this._checkBox.checked)
            return true;

        // Show the warning if updates have already been triggered, but
        // the user doesn't have enough permissions to cancel them.
        let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;
        return this._updateInfo.UpdatePrepared && this._updateInfo.UpdateTriggered && !updatesAllowed;
    }

    _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.checked)
            subject = dialogContent.subjectWithUpdates;

        this._batteryWarning.visible = this._shouldShowLowBatteryWarning(dialogContent);

        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 the PackageKit proxy is available (i.e. PackageKit is available).
        if (dialogContent.upgradeDescription) {
            const { name, version } = this._updateInfo.PreparedUpgrade;
            if (name != null && version != null)
                description = dialogContent.upgradeDescription(name, version);
        }

        // Fall back to regular description
        if (!description)
            description = dialogContent.description(displayTime);

        this._messageDialogContent.title = subject;
        this._messageDialogContent.description = description;

        let hasApplications = this._applications.length > 0;
        let hasSessions = this._sessions.length > 0;

        this._applicationSection.visible = hasApplications;
        this._sessionSection.visible = hasSessions;
    }

    _onCapturedEvent(actor, event) {
        let altEnabled = false;

        let type = event.type();
        if (type !== Clutter.EventType.KEY_PRESS && type !== Clutter.EventType.KEY_RELEASE)
            return Clutter.EVENT_PROPAGATE;

        let key = event.get_key_symbol();
        if (key !== Clutter.KEY_Alt_L && key !== Clutter.KEY_Alt_R)
            return Clutter.EVENT_PROPAGATE;

        if (type === Clutter.EventType.KEY_PRESS)
            altEnabled = true;

        this._rebootButton.visible = !altEnabled;
        this._rebootButtonAlt.visible = altEnabled;

        return Clutter.EVENT_PROPAGATE;
    }

    _updateButtons() {
        this.clearButtons();

        this.addButton({
            action: this.cancel.bind(this),
            label: _('Cancel'),
            key: Clutter.KEY_Escape,
        });

        let dialogContent = DialogContent[this._type];
        for (let i = 0; i < dialogContent.confirmButtons.length; i++) {
            let signal = dialogContent.confirmButtons[i].signal;
            let label = dialogContent.confirmButtons[i].label;
            let button = this.addButton({
                action: () => {
                    this.close(true);
                    let signalId = this.connect('closed', () => {
                        this.disconnect(signalId);
                        this._confirm(signal);
                    });
                },
                label,
            });

            // Add Alt "Boot Options" option to the Reboot button
            if (this._canRebootToBootLoaderMenu && signal === 'ConfirmedReboot') {
                this._rebootButton = button;
                this._rebootButtonAlt = this.addButton({
                    action: () => {
                        this.close(true);
                        let signalId = this.connect('closed', () => {
                            this.disconnect(signalId);
                            this._confirmRebootToBootLoaderMenu();
                        });
                    },
                    label: C_('button', 'Boot Options'),
                });
                this._rebootButtonAlt.visible = false;
                this._capturedEventId = this.connect('captured-event',
                    this._onCapturedEvent.bind(this));
            }
        }
    }

    _stopAltCapture() {
        if (this._capturedEventId > 0) {
            global.stage.disconnect(this._capturedEventId);
            this._capturedEventId = 0;
        }
        this._rebootButton = null;
        this._rebootButtonAlt = null;
    }

    close(skipSignal) {
        super.close();

        if (!skipSignal)
            this._dbusImpl.emit_signal('Closed', null);
    }

    cancel() {
        this._stopTimer();
        this._stopAltCapture();
        this._dbusImpl.emit_signal('Canceled', null);
        this.close();
    }

    _confirmRebootToBootLoaderMenu() {
        this._loginManager.setRebootToBootLoaderMenu();
        this._confirm('ConfirmedReboot');
    }

    _confirm(signal) {
        let callback = () => {
            this._fadeOutDialog();
            this._stopTimer();
            this._stopAltCapture();
            this._dbusImpl.emit_signal(signal, null);
        };

        // Offline update not available; just emit the signal
        if (!this._checkBox.visible) {
            callback();
            return;
        }

        // Trigger the offline update as requested
        if (this._checkBox.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) {
        // Handle this gracefully if PackageKit is not available.
        if (!this._pkOfflineProxy) {
            callback();
            return;
        }

        this._pkOfflineProxy.TriggerRemote('reboot', (result, error) => {
            if (error)
                log(error.message);

            callback();
        });
    }

    _triggerOfflineUpdateShutdown(callback) {
        // Handle this gracefully if PackageKit is not available.
        if (!this._pkOfflineProxy) {
            callback();
            return;
        }

        this._pkOfflineProxy.TriggerRemote('power-off', (result, error) => {
            if (error)
                log(error.message);

            callback();
        });
    }

    _triggerOfflineUpdateCancel(callback) {
        // Handle this gracefully if PackageKit is not available.
        if (!this._pkOfflineProxy) {
            callback();
            return;
        }

        this._pkOfflineProxy.CancelRemote((result, error) => {
            if (error)
                log(error.message);

            callback();
        });
    }

    _startTimer() {
        let startTime = GLib.get_monotonic_time();
        this._secondsLeft = this._totalSecondsToStayOpen;

        this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 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) {
            GLib.source_remove(this._timerId);
            this._timerId = 0;
        }

        this._secondsLeft = 0;
    }

    _onInhibitorLoaded(inhibitor) {
        if (!this._applications.includes(inhibitor)) {
            // Stale inhibitor
            return;
        }

        let app = findAppFromInhibitor(inhibitor);
        const [flags] = app ? inhibitor.GetFlagsSync() : [0];

        if (app && flags & GnomeSession.InhibitFlags.LOGOUT) {
            let [description] = inhibitor.GetReasonSync();
            let listItem = new Dialog.ListSectionItem({
                icon_actor: app.create_icon_texture(_ITEM_ICON_SIZE),
                title: app.get_name(),
                description,
            });
            this._applicationSection.list.add_child(listItem);
        } else {
            // inhibiting app is a service (not an application) or is not
            // inhibiting logout/shutdown
            this._applications.splice(this._applications.indexOf(inhibitor), 1);
        }

        this._sync();
    }

    _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;

                const session = {
                    user: this._userManager.get_user(userName),
                    username: userName,
                    type: proxy.Type,
                    remote: proxy.Remote,
                };
                this._sessions.push(session);

                let userAvatar = new UserWidget.Avatar(session.user, { iconSize: _ITEM_ICON_SIZE });
                userAvatar.update();

                userName = 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 listItem = new Dialog.ListSectionItem({
                    icon_actor: userAvatar,
                    title: userLabelText,
                });
                this._sessionSection.list.add_child(listItem);

                // limit the number of entries
                n++;
                if (n == MAX_USERS_IN_SESSION_DIALOG)
                    break;
            }

            this._sync();
        });
    }

    async _getUpdateInfo() {
        const connection = this._pkOfflineProxy.get_connection();
        const reply = await connection.call(
            this._pkOfflineProxy.g_name,
            this._pkOfflineProxy.g_object_path,
            'org.freedesktop.DBus.Properties',
            'GetAll',
            new GLib.Variant('(s)', [this._pkOfflineProxy.g_interface_name]),
            null,
            Gio.DBusCallFlags.NONE,
            -1,
            null);
        const [info] = reply.recursiveUnpack();
        return info;
    }

    async OpenAsync(parameters, invocation) {
        let [type, timestamp, totalSecondsToStayOpen, inhibitorObjectPaths] = parameters;
        this._totalSecondsToStayOpen = totalSecondsToStayOpen;
        this._type = type;

        try {
            this._updateInfo = await this._getUpdateInfo();
        } catch (e) {
            if (this._pkOfflineProxy !== null)
                log(`Failed to get update info from PackageKit: ${e.message}`);

            this._updateInfo = {
                UpdateTriggered: false,
                UpdatePrepared: false,
                UpgradeTriggered: false,
                PreparedUpgrade: {},
            };
        }

        // Only consider updates and upgrades if PackageKit is available.
        if (this._pkOfflineProxy && this._type == DialogType.RESTART) {
            if (this._updateInfo.UpdateTriggered)
                this._type = DialogType.UPDATE_RESTART;
            else if (this._updateInfo.UpgradeTriggered)
                this._type = DialogType.UPGRADE_RESTART;
        }

        this._applications = [];
        this._applicationSection.list.destroy_all_children();

        this._sessions = [];
        this._sessionSection.list.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 => {
                this._onInhibitorLoaded(proxy);
            });

            this._applications.push(inhibitor);
        }

        if (dialogContent.showOtherSessions)
            this._loadSessions();

        let updatesAllowed = this._updatesPermission && this._updatesPermission.allowed;

        _setCheckBoxLabel(this._checkBox, dialogContent.checkBoxText || '');
        this._checkBox.visible = dialogContent.checkBoxText && this._updateInfo.UpdatePrepared && updatesAllowed;

        if (this._type === DialogType.UPGRADE_RESTART)
            this._checkBox.checked = this._checkBox.visible && this._updateInfo.UpdateTriggered && !this._isDischargingBattery();
        else
            this._checkBox.checked = this._checkBox.visible && !this._isBatteryLow();

        this._batteryWarning.visible = this._shouldShowLowBatteryWarning(dialogContent);

        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)lightbox.js�&// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Lightbox */

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

const Params = imports.misc.params;

var DEFAULT_FADE_FACTOR = 0.4;
var VIGNETTE_BRIGHTNESS = 0.5;
var VIGNETTE_SHARPNESS = 0.7;

const VIGNETTE_DECLARATIONS = '                                              \
uniform float brightness;                                                  \n\
uniform float vignette_sharpness;                                          \n\
float rand(vec2 p) {                                                       \n\
  return fract(sin(dot(p, vec2(12.9898, 78.233))) * 43758.5453123);        \n\
}                                                                          \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 = clamp(length(1.41421 * position), 0.0, 1.0);                     \n\
float pixel_brightness = mix(1.0, 1.0 - vignette_sharpness, t);            \n\
cogl_color_out.a *= 1.0 - pixel_brightness * brightness;                   \n\
cogl_color_out.a += (rand(position) - 0.5) / 100.0;                        \n';


var RadialShaderEffect = GObject.registerClass({
    Properties: {
        'brightness': GObject.ParamSpec.float(
            'brightness', 'brightness', 'brightness',
            GObject.ParamFlags.READWRITE,
            0, 1, 1),
        'sharpness': GObject.ParamSpec.float(
            'sharpness', 'sharpness', 'sharpness',
            GObject.ParamFlags.READWRITE,
            0, 1, 0),
    },
}, class RadialShaderEffect extends Shell.GLSLEffect {
    _init(params) {
        this._brightness = undefined;
        this._sharpness = undefined;

        super._init(params);

        this._brightnessLocation = this.get_uniform_location('brightness');
        this._sharpnessLocation = this.get_uniform_location('vignette_sharpness');

        this.brightness = 1.0;
        this.sharpness = 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) {
        if (this._brightness === v)
            return;
        this._brightness = v;
        this.set_uniform_float(this._brightnessLocation,
            1, [this._brightness]);
        this.notify('brightness');
    }

    get sharpness() {
        return this._sharpness;
    }

    set sharpness(v) {
        if (this._sharpness === v)
            return;
        this._sharpness = v;
        this.set_uniform_float(this._sharpnessLocation,
            1, [this._sharpness]);
        this.notify('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
 *           - fadeFactor: fading opacity factor
 *           - radialEffect: whether to enable the GLSL radial effect
 *
 * 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 = GObject.registerClass({
    Properties: {
        'active': GObject.ParamSpec.boolean(
            'active', 'active', 'active', GObject.ParamFlags.READABLE, false),
    },
}, class Lightbox extends St.Bin {
    _init(container, params) {
        params = Params.parse(params, {
            inhibitEvents: false,
            width: null,
            height: null,
            fadeFactor: DEFAULT_FADE_FACTOR,
            radialEffect: false,
        });

        super._init({
            reactive: params.inhibitEvents,
            width: params.width,
            height: params.height,
            visible: false,
        });

        this._active = false;
        this._container = container;
        this._children = container.get_children();
        this._fadeFactor = params.fadeFactor;
        this._radialEffect = params.radialEffect;

        if (this._radialEffect)
            this.add_effect(new RadialShaderEffect({ name: 'radial' }));
        else
            this.set({ opacity: 0, style_class: 'lightbox' });

        container.add_actor(this);
        container.set_child_above_sibling(this, null);

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

        if (!params.width || !params.height) {
            this.add_constraint(new Clutter.BindConstraint({
                source: container,
                coordinate: Clutter.BindCoordinate.ALL,
            }));
        }

        container.connectObject(
            'actor-added', this._actorAdded.bind(this),
            'actor-removed', this._actorRemoved.bind(this), this);

        this._highlighted = null;
    }

    get active() {
        return this._active;
    }

    _actorAdded(container, newChild) {
        let children = this._container.get_children();
        let myIndex = children.indexOf(this);
        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.
            this._container.set_child_above_sibling(this, newChild);
            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);
        }
    }

    lightOn(fadeInTime) {
        this.remove_all_transitions();

        let easeProps = {
            duration: fadeInTime || 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        };

        let onComplete = () => {
            this._active = true;
            this.notify('active');
        };

        this.show();

        if (this._radialEffect) {
            this.ease_property(
                '@effects.radial.brightness', VIGNETTE_BRIGHTNESS, easeProps);
            this.ease_property(
                '@effects.radial.sharpness', VIGNETTE_SHARPNESS,
                Object.assign({ onComplete }, easeProps));
        } else {
            this.ease(Object.assign(easeProps, {
                opacity: 255 * this._fadeFactor,
                onComplete,
            }));
        }
    }

    lightOff(fadeOutTime) {
        this.remove_all_transitions();

        this._active = false;
        this.notify('active');

        let easeProps = {
            duration: fadeOutTime || 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        };

        let onComplete = () => this.hide();

        if (this._radialEffect) {
            this.ease_property(
                '@effects.radial.brightness', 1.0, easeProps);
            this.ease_property(
                '@effects.radial.sharpness', 0.0, Object.assign({ onComplete }, easeProps));
        } else {
            this.ease(Object.assign(easeProps, { opacity: 0, onComplete }));
        }
    }

    _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:
     * @param {Clutter.Actor=} 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;
        for (let i = this._children.length - 1; i >= 0; i--) {
            if (this._children[i] == window)
                this._container.set_child_above_sibling(this._children[i], null);
            else if (this._children[i] == this._highlighted)
                this._container.set_child_below_sibling(this._children[i], below);
            else
                below = this._children[i];
        }

        this._highlighted = window;
    }

    /**
     * _onDestroy:
     *
     * This is called when the lightbox' actor is destroyed, either
     * by destroying its container or by explicitly calling this.destroy().
     */
    _onDestroy() {
        this.highlight(null);
    }
});
(uuay)location.js�5// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Clutter, Gio, GLib, GObject, Shell, St } = 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 { 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 = 'location';
const APP_PERMISSIONS_ID = 'location';

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');

let _geoclueAgent = null;
function _getGeoclueAgent() {
    if (_geoclueAgent === null)
        _geoclueAgent = new GeoclueAgent();
    return _geoclueAgent;
}

var GeoclueAgent = GObject.registerClass({
    Properties: {
        'enabled': GObject.ParamSpec.boolean(
            'enabled', 'Enabled', 'Enabled',
            GObject.ParamFlags.READWRITE,
            false),
        'in-use': GObject.ParamSpec.boolean(
            'in-use', 'In use', 'In use',
            GObject.ParamFlags.READABLE,
            false),
        'max-accuracy-level': GObject.ParamSpec.int(
            'max-accuracy-level', 'Max accuracy level', 'Max accuracy level',
            GObject.ParamFlags.READABLE,
            0, 8, 0),
    },
}, class GeoclueAgent extends GObject.Object {
    _init() {
        super._init();

        this._settings = new Gio.Settings({ schema_id: LOCATION_SCHEMA });
        this._settings.connectObject(
            `changed::${ENABLED}`, () => this.notify('enabled'),
            `changed::${MAX_ACCURACY_LEVEL}`, () => this._onMaxAccuracyLevelChanged(),
            this);

        this._agent = Gio.DBusExportedObject.wrapJSObject(AgentIface, this);
        this._agent.export(Gio.DBus.system, '/org/freedesktop/GeoClue2/Agent');

        this.connect('notify::enabled', this._onMaxAccuracyLevelChanged.bind(this));

        this._watchId = Gio.bus_watch_name(Gio.BusType.SYSTEM,
                                           'org.freedesktop.GeoClue2',
                                           0,
                                           this._connectToGeoclue.bind(this),
                                           this._onGeoclueVanished.bind(this));
        this._onMaxAccuracyLevelChanged();
        this._connectToGeoclue();
        this._connectToPermissionStore();
    }

    get enabled() {
        return this._settings.get_boolean(ENABLED);
    }

    set enabled(value) {
        this._settings.set_boolean(ENABLED, value);
    }

    get inUse() {
        return this._managerProxy?.InUse ?? false;
    }

    get maxAccuracyLevel() {
        if (this.enabled) {
            let level = this._settings.get_string(MAX_ACCURACY_LEVEL);

            return GeoclueAccuracyLevel[level.toUpperCase()] ||
                   GeoclueAccuracyLevel.NONE;
        } else {
            return GeoclueAccuracyLevel.NONE;
        }
    }

    AuthorizeAppAsync(params, invocation) {
        let [desktopId, reqAccuracyLevel] = params;

        let authorizer = new AppAuthorizer(desktopId,
            reqAccuracyLevel, this._permStoreProxy, this.maxAccuracyLevel);

        authorizer.authorize(accuracyLevel => {
            let ret = accuracyLevel != GeoclueAccuracyLevel.NONE;
            invocation.return_value(GLib.Variant.new('(bu)',
                                                     [ret, accuracyLevel]));
        });
    }

    get MaxAccuracyLevel() {
        return this.maxAccuracyLevel;
    }

    _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._managerProxy.connectObject('g-properties-changed',
            this._onGeocluePropsChanged.bind(this), this);

        this.notify('in-use');

        this._managerProxy.AddAgentRemote('gnome-shell', this._onAgentRegistered.bind(this));
    }

    _onAgentRegistered(result, error) {
        this._connecting = false;
        this._notifyMaxAccuracyLevel();

        if (error != null)
            log(error.message);
    }

    _onGeoclueVanished() {
        this._managerProxy.disconnectObject(this);
        this._managerProxy = null;

        this.notify('in-use');
    }

    _onMaxAccuracyLevelChanged() {
        this.notify('max-accuracy-level');

        // 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();
    }

    _notifyMaxAccuracyLevel() {
        let variant = new GLib.Variant('u', this.maxAccuracyLevel);
        this._agent.emit_property_changed('MaxAccuracyLevel', variant);
    }

    _onGeocluePropsChanged(proxy, properties) {
        let unpacked = properties.deep_unpack();
        if ("InUse" in unpacked)
            this.notify('in-use');
    }

    _connectToPermissionStore() {
        this._permStoreProxy = null;
        new PermissionStore.PermissionStore(this._onPermStoreProxyReady.bind(this));
    }

    _onPermStoreProxyReady(proxy, error) {
        if (error != null) {
            log(error.message);
            return;
        }

        this._permStoreProxy = proxy;
    }
});

var Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        this._agent = _getGeoclueAgent();

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'find-location-symbolic';
        this._agent.bind_property('in-use',
            this._indicator,
            'visible',
            GObject.BindingFlags.SYNC_CREATE);

        this._item = new PopupMenu.PopupSubMenuMenuItem('', true);
        this._item.icon.icon_name = 'find-location-symbolic';
        this._agent.bind_property('in-use',
            this._item,
            'visible',
            GObject.BindingFlags.SYNC_CREATE);

        this._item.label.text = _('Location Enabled');
        this._onOffAction = this._item.menu.addAction(_('Disable'),
            () => (this._agent.enabled = !this._agent.enabled));
        this._item.menu.addSettingsAction(_('Privacy Settings'), 'gnome-location-panel.desktop');

        this.menu.addMenuItem(this._item);

        this._agent.connectObject(
            'notify::enabled', () => this._sync(),
            'notify::in-use', () => this._sync(), this);

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

    _onSessionUpdated() {
        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;
        this.menu.setSensitive(sensitive);
    }

    _sync() {
        if (this._agent.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');
        }
    }
});

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 = Math.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 = GObject.registerClass({
    Signals: { 'response': { param_types: [GObject.TYPE_UINT] } },
}, class GeolocationDialog extends ModalDialog.ModalDialog {
    _init(name, reason, reqAccuracyLevel) {
        super._init({ styleClass: 'geolocation-dialog' });
        this.reqAccuracyLevel = reqAccuracyLevel;

        let content = new Dialog.MessageDialogContent({
            title: _('Allow location access'),
            /* Translators: %s is an application name */
            description: _('The app %s wants to access your location').format(name),
        });

        let reasonLabel = new St.Label({
            text: reason,
            style_class: 'message-dialog-description',
        });
        content.add_child(reasonLabel);

        let infoLabel = new St.Label({
            text: _('Location access can be changed at any time from the privacy settings.'),
            style_class: 'message-dialog-description',
        });
        content.add_child(infoLabel);

        this.contentLayout.add_child(content);

        const 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();
    }
});
(uuay)accessibility.js\// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ATIndicator */

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

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_TEXT_SCALING_FACTOR       = 'text-scaling-factor';

const A11Y_INTERFACE_SCHEMA         = 'org.gnome.desktop.a11y.interface';
const KEY_HIGH_CONTRAST             = 'high-contrast';

var ATIndicator = GObject.registerClass(
class ATIndicator extends PanelMenu.Button {
    _init() {
        super._init(0.5, _("Accessibility"));

        this.add_child(new St.Icon({
            style_class: 'system-status-icon',
            icon_name: 'org.gnome.Settings-accessibility-symbolic',
        }));

        this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });
        this._a11ySettings.connect(`changed::${KEY_ALWAYS_SHOW}`, this._queueSyncMenuVisibility.bind(this));

        let highContrast = this._buildItem(_('High Contrast'), A11Y_INTERFACE_SCHEMA, KEY_HIGH_CONTRAST);
        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.visible = alwaysShow || items.some(f => !!f.state);

        return GLib.SOURCE_REMOVE;
    }

    _queueSyncMenuVisibility() {
        if (this._syncMenuVisibilityIdle)
            return;

        this._syncMenuVisibilityIdle = GLib.idle_add(GLib.PRIORITY_DEFAULT, this._syncMenuVisibility.bind(this));
        GLib.Source.set_name_by_id(this._syncMenuVisibilityIdle, '[gnome-shell] this._syncMenuVisibility');
    }

    _buildItemExtended(string, initialValue, writable, onSet) {
        let widget = new PopupMenu.PopupSwitchMenuItem(string, initialValue);
        if (!writable) {
            widget.reactive = false;
        } else {
            widget.connect('toggled', item => {
                onSet(item.state);
            });
        }
        return widget;
    }

    _buildItem(string, schema, key) {
        let settings = new Gio.Settings({ schema_id: schema });
        let widget = this._buildItemExtended(string,
            settings.get_boolean(key),
            settings.is_writable(key),
            enabled => settings.set_boolean(key, enabled));

        settings.connect(`changed::${key}`, () => {
            widget.setToggleState(settings.get_boolean(key));

            this._queueSyncMenuVisibility();
        });

        return widget;
    }

    _buildFontItem() {
        let settings = new Gio.Settings({ schema_id: DESKTOP_INTERFACE_SCHEMA });
        let factor = settings.get_double(KEY_TEXT_SCALING_FACTOR);
        let initialSetting = factor > 1.0;
        let widget = this._buildItemExtended(_("Large Text"),
            initialSetting,
            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);
                }
            });

        settings.connect(`changed::${KEY_TEXT_SCALING_FACTOR}`, () => {
            factor = settings.get_double(KEY_TEXT_SCALING_FACTOR);
            let active = factor > 1.0;
            widget.setToggleState(active);

            this._queueSyncMenuVisibility();
        });

        return widget;
    }
});
(uuay)parentalControlsManager.js7// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
//
// Copyright (C) 2018, 2019, 2020 Endless Mobile, Inc.
//
// This is a GNOME Shell component to wrap the interactions over
// D-Bus with the malcontent library.
//
// Licensed under the GNU General Public License Version 2
//
// 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
// of the License, 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, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

/* exported getDefault */

const { Gio, GObject, Shell } = imports.gi;

// We require libmalcontent ≥ 0.6.0
const HAVE_MALCONTENT = imports.package.checkSymbol(
    'Malcontent', '0', 'ManagerGetValueFlags');

var Malcontent = null;
if (HAVE_MALCONTENT) {
    Malcontent = imports.gi.Malcontent;
    Gio._promisify(Malcontent.Manager.prototype, 'get_app_filter_async');
}

let _singleton = null;

function getDefault() {
    if (_singleton === null)
        _singleton = new ParentalControlsManager();

    return _singleton;
}

// A manager class which provides cached access to the constructing user’s
// parental controls settings. It’s possible for the user’s parental controls
// to change at runtime if the Parental Controls application is used by an
// administrator from within the user’s session.
var ParentalControlsManager = GObject.registerClass({
    Signals: {
        'app-filter-changed': {},
    },
}, class ParentalControlsManager extends GObject.Object {
    _init() {
        super._init();

        this._initialized = false;
        this._disabled = false;
        this._appFilter = null;

        this._initializeManager();
    }

    async _initializeManager() {
        if (!HAVE_MALCONTENT) {
            console.debug('Skipping parental controls support, malcontent not found');
            this._initialized = true;
            this.emit('app-filter-changed');
            return;
        }

        try {
            const connection = await Gio.DBus.get(Gio.BusType.SYSTEM, null);
            this._manager = new Malcontent.Manager({ connection });
            this._appFilter = await this._manager.get_app_filter_async(
                Shell.util_get_uid(),
                Malcontent.ManagerGetValueFlags.NONE,
                null);
        } catch (e) {
            if (e.matches(Malcontent.ManagerError, Malcontent.ManagerError.DISABLED)) {
                console.debug('Parental controls globally disabled');
                this._disabled = true;
            } else {
                logError(e, 'Failed to get parental controls settings');
                return;
            }
        }

        this._manager.connect('app-filter-changed', this._onAppFilterChanged.bind(this));

        // Signal initialisation is complete.
        this._initialized = true;
        this.emit('app-filter-changed');
    }

    async _onAppFilterChanged(manager, uid) {
        // Emit 'changed' signal only if app-filter is changed for currently logged-in user.
        let currentUid = Shell.util_get_uid();
        if (currentUid !== uid)
            return;

        try {
            this._appFilter = await this._manager.get_app_filter_async(
                currentUid,
                Malcontent.ManagerGetValueFlags.NONE,
                null);
            this.emit('app-filter-changed');
        } catch (e) {
            // Log an error and keep the old app filter.
            logError(e, `Failed to get new MctAppFilter for uid ${Shell.util_get_uid()} on app-filter-changed`);
        }
    }

    get initialized() {
        return this._initialized;
    }

    // Calculate whether the given app (a Gio.DesktopAppInfo) should be shown
    // on the desktop, in search results, etc. The app should be shown if:
    //  - The .desktop file doesn’t say it should be hidden.
    //  - The executable from the .desktop file’s Exec line isn’t denied in
    //    the user’s parental controls.
    //  - None of the flatpak app IDs from the X-Flatpak and the
    //    X-Flatpak-RenamedFrom lines are denied in the user’s parental
    //    controls.
    shouldShowApp(appInfo) {
        // Quick decision?
        if (!appInfo.should_show())
            return false;

        // Are parental controls enabled (at configure time or runtime)?
        if (!HAVE_MALCONTENT || this._disabled)
            return true;

        // Have we finished initialising yet?
        if (!this.initialized) {
            console.debug(`Hiding app because parental controls not yet initialised: ${appInfo.get_id()}`);
            return false;
        }

        return this._appFilter.is_appinfo_allowed(appInfo);
    }
});
(uuay)workspace.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Workspace */

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

const Background = imports.ui.background;
const DND = imports.ui.dnd;
const Main = imports.ui.main;
const OverviewControls = imports.ui.overviewControls;
const Params = imports.misc.params;
const Util = imports.misc.util;
const { WindowPreview } = imports.ui.windowPreview;

var WINDOW_PREVIEW_MAXIMUM_SCALE = 0.95;

var WINDOW_REPOSITIONING_DELAY = 750;

// 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;

const BACKGROUND_CORNER_RADIUS_PIXELS = 30;
const BACKGROUND_MARGIN = 12;

// 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.
//
// 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_PREVIEW_MAXIMUM_SCALE, which means that the inequality:
//
//   windowScale * layoutScale <= WINDOW_PREVIEW_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_PREVIEW_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(params) {
        params = Params.parse(params, {
            monitor: null,
            rowSpacing: 0,
            columnSpacing: 0,
        });

        if (!params.monitor)
            throw new Error(`No monitor param passed to ${this.constructor.name}`);

        this._monitor = params.monitor;
        this._rowSpacing = params.rowSpacing;
        this._columnSpacing = params.columnSpacing;
    }

    // Compute a strategy-specific overall layout given a list of WindowPreviews
    // @windows and the strategy-specific @layoutParams.
    //
    // Returns a strategy-specific layout object that is opaque to the user.
    computeLayout(_windows, _layoutParams) {
        throw new GObject.NotImplementedError(`computeLayout in ${this.constructor.name}`);
    }

    // Given @layout and @area, compute the overall scale of the layout and
    // space occupied by the layout.
    //
    // This method returns an array where the first element is the scale and
    // the second element is the space.
    //
    // This method must be called before calling computeWindowSlots(), as it
    // sets the fixed overall scale of the layout.
    computeScaleAndSpace(_layout, _area) {
        throw new GObject.NotImplementedError(`computeScaleAndSpace in ${this.constructor.name}`);
    }

    // Returns an array with final position and size information for each
    // window of the layout, given a bounding area that it will be inside of.
    computeWindowSlots(_layout, _area) {
        throw new GObject.NotImplementedError(`computeWindowSlots in ${this.constructor.name}`);
    }
};

var UnalignedLayoutStrategy = class extends LayoutStrategy {
    _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 overall 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.boundingBox.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 Util.lerp(1.5, 1, ratio);
    }

    _computeRowSizes(layout) {
        let { rows, 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.
        // This affects in what order the windows end up in a row.
        row.windows.sort((a, b) => a.windowCenter.x - b.windowCenter.x);
    }

    computeLayout(windows, layoutParams) {
        layoutParams = Params.parse(layoutParams, {
            numRows: 0,
        });

        if (layoutParams.numRows === 0)
            throw new Error(`${this.constructor.name}: No numRows given in layout params`);

        const numRows = layoutParams.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.boundingBox.width * s;
        }

        let idealRowWidth = totalWidth / numRows;

        // Sort windows vertically to minimize travel distance.
        // This affects what rows the windows get placed in.
        let sortedWindows = windows.slice();
        sortedWindows.sort((a, b) => a.windowCenter.y - b.windowCenter.y);

        let windowIdx = 0;
        for (let i = 0; i < numRows; i++) {
            let row = this._newRow();
            rows.push(row);

            for (; windowIdx < sortedWindows.length; windowIdx++) {
                let window = sortedWindows[windowIdx];
                let s = this._computeWindowScale(window);
                let width = window.boundingBox.width * s;
                let height = window.boundingBox.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;
        }

        return {
            numRows,
            rows,
            maxColumns: maxRow.windows.length,
            gridWidth: maxRow.fullWidth,
            gridHeight,
        };
    }

    computeScaleAndSpace(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_PREVIEW_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;

        return [scale, space];
    }

    computeWindowSlots(layout, area) {
        this._computeRowSizes(layout);

        let { rows, 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 /= 2;

        for (let i = 0; i < rows.length; i++) {
            const row = rows[i];
            const rowY = row.y + compensation;
            const rowHeight = row.height * row.additionalScale;

            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.boundingBox.width * s;
                let cellHeight = window.boundingBox.height * s;

                s = Math.min(s, WINDOW_PREVIEW_MAXIMUM_SCALE);
                let cloneWidth = window.boundingBox.width * s;
                const cloneHeight = window.boundingBox.height * s;

                let cloneX = x + (cellWidth - cloneWidth) / 2;
                let cloneY;

                // If there's only one row, align windows vertically centered inside the row
                if (rows.length === 1)
                    cloneY = rowY + (rowHeight - cloneHeight) / 2;
                // If there are multiple rows, align windows to the bottom edge of the row
                else
                    cloneY = rowY + rowHeight - cellHeight;

                // Align with the pixel grid to prevent blurry windows at scale = 1
                cloneX = Math.floor(cloneX);
                cloneY = Math.floor(cloneY);

                slots.push([cloneX, cloneY, cloneWidth, cloneHeight, window]);
                x += cellWidth + this._columnSpacing;
            }
        }
        return slots;
    }
};

function animateAllocation(actor, box) {
    actor.save_easing_state();
    actor.set_easing_mode(Clutter.AnimationMode.EASE_OUT_QUAD);
    actor.set_easing_duration(200);

    actor.allocate(box);

    actor.restore_easing_state();

    return actor.get_transition('allocation');
}

var WorkspaceLayout = GObject.registerClass({
    Properties: {
        'spacing': GObject.ParamSpec.double(
            'spacing', 'Spacing', 'Spacing',
            GObject.ParamFlags.READWRITE,
            0, Infinity, 20),
        'layout-frozen': GObject.ParamSpec.boolean(
            'layout-frozen', 'Layout frozen', 'Layout frozen',
            GObject.ParamFlags.READWRITE,
            false),
    },
}, class WorkspaceLayout extends Clutter.LayoutManager {
    _init(metaWorkspace, monitorIndex, overviewAdjustment) {
        super._init();

        this._spacing = 20;
        this._layoutFrozen = false;

        this._metaWorkspace = metaWorkspace;
        this._monitorIndex = monitorIndex;
        this._overviewAdjustment = overviewAdjustment;

        this._container = null;
        this._windows = new Map();
        this._sortedWindows = [];
        this._lastBox = null;
        this._windowSlots = [];
        this._layout = null;

        this._needsLayout = true;

        this._stateAdjustment = new St.Adjustment({
            value: 0,
            lower: 0,
            upper: 1,
        });

        this._stateAdjustment.connect('notify::value', () => {
            this._syncOpacities();
            this.syncOverlays();
            this.layout_changed();
        });

        this._workarea = null;
        this._workareasChangedId = 0;
    }

    _syncOpacity(actor, metaWindow) {
        if (!metaWindow.showing_on_its_workspace())
            actor.opacity = this._stateAdjustment.value * 255;
    }

    _syncOpacities() {
        this._windows.forEach(({ metaWindow }, actor) => {
            this._syncOpacity(actor, metaWindow);
        });
    }

    _isBetterScaleAndSpace(oldScale, oldSpace, scale, space) {
        let spacePower = (space - oldSpace) * LAYOUT_SPACE_WEIGHT;
        let scalePower = (scale - oldScale) * LAYOUT_SCALE_WEIGHT;

        if (scale > oldScale && space > oldSpace) {
            // Win win -- better scale and better space
            return true;
        } else if (scale > oldScale && space <= oldSpace) {
            // Keep new layout only if scale gain outweighs aspect space loss
            return scalePower > spacePower;
        } else if (scale <= oldScale && space > oldSpace) {
            // Keep new layout only if aspect space gain outweighs scale loss
            return spacePower > scalePower;
        } else {
            // Lose -- worse scale and space
            return false;
        }
    }

    _adjustSpacingAndPadding(rowSpacing, colSpacing, containerBox) {
        if (this._sortedWindows.length === 0)
            return [rowSpacing, colSpacing, containerBox];

        // All of the overlays have the same chrome sizes,
        // so just pick the first one.
        const window = this._sortedWindows[0];

        const [topOversize, bottomOversize] = window.chromeHeights();
        const [leftOversize, rightOversize] = window.chromeWidths();

        const oversize =
            Math.max(topOversize, bottomOversize, leftOversize, rightOversize);

        if (rowSpacing !== null)
            rowSpacing += oversize;
        if (colSpacing !== null)
            colSpacing += oversize;

        if (containerBox) {
            const monitor = Main.layoutManager.monitors[this._monitorIndex];

            const bottomPoint = new Graphene.Point3D({ y: containerBox.y2 });
            const transformedBottomPoint =
                this._container.apply_transform_to_point(bottomPoint);
            const bottomFreeSpace =
                (monitor.y + monitor.height) - transformedBottomPoint.y;

            const [, bottomOverlap] = window.overlapHeights();

            if ((bottomOverlap + oversize) > bottomFreeSpace)
                containerBox.y2 -= (bottomOverlap + oversize) - bottomFreeSpace;
        }

        return [rowSpacing, colSpacing, containerBox];
    }

    _createBestLayout(area) {
        const [rowSpacing, columnSpacing] =
            this._adjustSpacingAndPadding(this._spacing, this._spacing, null);

        // We look for the largest scale that allows us to fit the
        // largest row/tallest column on the workspace.
        this._layoutStrategy = new UnalignedLayoutStrategy({
            monitor: Main.layoutManager.monitors[this._monitorIndex],
            rowSpacing,
            columnSpacing,
        });

        let lastLayout = null;
        let lastNumColumns = -1;
        let lastScale = 0;
        let lastSpace = 0;

        for (let numRows = 1; ; numRows++) {
            const numColumns = Math.ceil(this._sortedWindows.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 === lastNumColumns)
                break;

            const layout = this._layoutStrategy.computeLayout(this._sortedWindows, {
                numRows,
            });

            const [scale, space] = this._layoutStrategy.computeScaleAndSpace(layout, area);

            if (lastLayout && !this._isBetterScaleAndSpace(lastScale, lastSpace, scale, space))
                break;

            lastLayout = layout;
            lastNumColumns = numColumns;
            lastScale = scale;
            lastSpace = space;
        }

        return lastLayout;
    }

    _getWindowSlots(containerBox) {
        [, , containerBox] =
            this._adjustSpacingAndPadding(null, null, containerBox);

        const availArea = {
            x: parseInt(containerBox.x1),
            y: parseInt(containerBox.y1),
            width: parseInt(containerBox.get_width()),
            height: parseInt(containerBox.get_height()),
        };

        return this._layoutStrategy.computeWindowSlots(this._layout, availArea);
    }

    _getAdjustedWorkarea(container) {
        const workarea = this._workarea.copy();

        if (container instanceof St.Widget) {
            const themeNode = container.get_theme_node();
            workarea.width -= themeNode.get_horizontal_padding();
            workarea.height -= themeNode.get_vertical_padding();
        }

        return workarea;
    }

    _syncWorkareaTracking() {
        if (this._container) {
            if (this._workAreaChangedId)
                return;
            this._workarea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
            this._workareasChangedId =
                global.display.connect('workareas-changed', () => {
                    this._workarea = Main.layoutManager.getWorkAreaForMonitor(this._monitorIndex);
                    this.layout_changed();
                });
        } else if (this._workareasChangedId) {
            global.display.disconnect(this._workareasChangedId);
            this._workareasChangedId = 0;
        }
    }

    vfunc_set_container(container) {
        this._container = container;
        this._syncWorkareaTracking();
        this._stateAdjustment.actor = container;
    }

    vfunc_get_preferred_width(container, forHeight) {
        const workarea = this._getAdjustedWorkarea(container);
        if (forHeight === -1)
            return [0, workarea.width];

        const workAreaAspectRatio = workarea.width / workarea.height;
        const widthPreservingAspectRatio = forHeight * workAreaAspectRatio;

        return [0, widthPreservingAspectRatio];
    }

    vfunc_get_preferred_height(container, forWidth) {
        const workarea = this._getAdjustedWorkarea(container);
        if (forWidth === -1)
            return [0, workarea.height];

        const workAreaAspectRatio = workarea.width / workarea.height;
        const heightPreservingAspectRatio = forWidth / workAreaAspectRatio;

        return [0, heightPreservingAspectRatio];
    }

    vfunc_allocate(container, box) {
        const containerBox = container.allocation;
        const [containerWidth, containerHeight] = containerBox.get_size();
        const containerAllocationChanged =
            this._lastBox === null || !this._lastBox.equal(containerBox);

        // If the containers size changed, we can no longer keep around
        // the old windowSlots, so we must unfreeze the layout.
        //
        // However, if the overview animation is in progress, don't unfreeze
        // the layout. This is needed to prevent windows "snapping" to their
        // new positions during the overview closing animation when the
        // allocation subtly expands every frame.
        if (this._layoutFrozen && containerAllocationChanged && !Main.overview.animationInProgress) {
            this._layoutFrozen = false;
            this.notify('layout-frozen');
        }

        const { ControlsState } = OverviewControls;
        const { currentState } =
            this._overviewAdjustment.getStateTransitionParams();
        const inSessionTransition = currentState <= ControlsState.WINDOW_PICKER;

        const window = this._sortedWindows[0];

        if (inSessionTransition || !window) {
            container.remove_clip();
        } else {
            const [, bottomOversize] = window.chromeHeights();
            const [containerX, containerY] = containerBox.get_origin();

            const extraHeightProgress =
                currentState - OverviewControls.ControlsState.WINDOW_PICKER;

            const extraClipHeight = bottomOversize * (1 - extraHeightProgress);

            container.set_clip(containerX, containerY,
                containerWidth, containerHeight + extraClipHeight);
        }

        let layoutChanged = false;
        if (!this._layoutFrozen || !this._lastBox) {
            if (this._needsLayout) {
                this._layout = this._createBestLayout(this._workarea);
                this._needsLayout = false;
                layoutChanged = true;
            }

            if (layoutChanged || containerAllocationChanged) {
                this._windowSlotsBox = box.copy();
                this._windowSlots = this._getWindowSlots(this._windowSlotsBox);
            }
        }

        const slotsScale = box.get_width() / this._windowSlotsBox.get_width();
        const workareaX = this._workarea.x;
        const workareaY = this._workarea.y;
        const workareaWidth = this._workarea.width;
        const stateAdjustementValue = this._stateAdjustment.value;

        const allocationScale = containerWidth / workareaWidth;

        const childBox = new Clutter.ActorBox();

        const nSlots = this._windowSlots.length;
        for (let i = 0; i < nSlots; i++) {
            let [x, y, width, height, child] = this._windowSlots[i];
            if (!child.visible)
                continue;

            x *= slotsScale;
            y *= slotsScale;
            width *= slotsScale;
            height *= slotsScale;

            const windowInfo = this._windows.get(child);

            let workspaceBoxX, workspaceBoxY;
            let workspaceBoxWidth, workspaceBoxHeight;

            if (windowInfo.metaWindow.showing_on_its_workspace()) {
                workspaceBoxX = (child.boundingBox.x - workareaX) * allocationScale;
                workspaceBoxY = (child.boundingBox.y - workareaY) * allocationScale;
                workspaceBoxWidth = child.boundingBox.width * allocationScale;
                workspaceBoxHeight = child.boundingBox.height * allocationScale;
            } else {
                workspaceBoxX = workareaX * allocationScale;
                workspaceBoxY = workareaY * allocationScale;
                workspaceBoxWidth = 0;
                workspaceBoxHeight = 0;
            }

            // Don't allow the scaled floating size to drop below
            // the target layout size.
            // We only want to apply this when the scaled floating size is
            // actually larger than the target layout size, that is while
            // animating between the session and the window picker.
            if (inSessionTransition) {
                workspaceBoxWidth = Math.max(workspaceBoxWidth, width);
                workspaceBoxHeight = Math.max(workspaceBoxHeight, height);
            }

            x = Util.lerp(workspaceBoxX, x, stateAdjustementValue);
            y = Util.lerp(workspaceBoxY, y, stateAdjustementValue);
            width = Util.lerp(workspaceBoxWidth, width, stateAdjustementValue);
            height = Util.lerp(workspaceBoxHeight, height, stateAdjustementValue);

            childBox.set_origin(x, y);
            childBox.set_size(width, height);

            if (windowInfo.currentTransition) {
                windowInfo.currentTransition.get_interval().set_final(childBox);

                // The timeline of the transition might not have been updated
                // before this allocation cycle, so make sure the child
                // still updates needs_allocation to FALSE.
                // Unfortunately, this relies on the fast paths in
                // clutter_actor_allocate(), otherwise we'd start a new
                // transition on the child, replacing the current one.
                child.allocate(child.allocation);
                continue;
            }

            // We want layout changes (ie. larger changes to the layout like
            // reshuffling the window order) to be animated, but small changes
            // like changes to the container size to happen immediately (for
            // example if the container height is being animated, we want to
            // avoid animating the children allocations to make sure they
            // don't "lag behind" the other animation).
            if (layoutChanged && !Main.overview.animationInProgress) {
                const transition = animateAllocation(child, childBox);
                if (transition) {
                    windowInfo.currentTransition = transition;
                    windowInfo.currentTransition.connect('stopped', () => {
                        windowInfo.currentTransition = null;
                    });
                }
            } else {
                child.allocate(childBox);
            }
        }

        this._lastBox = containerBox.copy();
    }

    _syncOverlay(preview) {
        const active = this._metaWorkspace?.active ?? true;
        preview.overlayEnabled = active && this._stateAdjustment.value === 1;
    }

    /**
     * syncOverlays:
     *
     * Synchronizes the overlay state of all window previews.
     */
    syncOverlays() {
        [...this._windows.keys()].forEach(preview => this._syncOverlay(preview));
    }

    /**
     * addWindow:
     * @param {WindowPreview} window: the window to add
     * @param {Meta.Window} metaWindow: the MetaWindow of the window
     *
     * Adds @window to the workspace, it will be shown immediately if
     * the layout isn't frozen using the layout-frozen property.
     *
     * If @window is already part of the workspace, nothing will happen.
     */
    addWindow(window, metaWindow) {
        if (this._windows.has(window))
            return;

        this._windows.set(window, {
            metaWindow,
            sizeChangedId: metaWindow.connect('size-changed', () => {
                this._needsLayout = true;
                this.layout_changed();
            }),
            destroyId: window.connect('destroy', () =>
                this.removeWindow(window)),
            currentTransition: null,
        });

        this._sortedWindows.push(window);
        this._sortedWindows.sort((a, b) => {
            const winA = this._windows.get(a).metaWindow;
            const winB = this._windows.get(b).metaWindow;

            return winA.get_stable_sequence() - winB.get_stable_sequence();
        });

        this._syncOpacity(window, metaWindow);
        this._syncOverlay(window);
        this._container.add_child(window);

        this._needsLayout = true;
        this.layout_changed();
    }

    /**
     * removeWindow:
     * @param {WindowPreview} window: the window to remove
     *
     * Removes @window from the workspace if @window is a part of the
     * workspace. If the layout-frozen property is set to true, the
     * window will still be visible until the property is set to false.
     */
    removeWindow(window) {
        const windowInfo = this._windows.get(window);
        if (!windowInfo)
            return;

        windowInfo.metaWindow.disconnect(windowInfo.sizeChangedId);
        window.disconnect(windowInfo.destroyId);
        if (windowInfo.currentTransition)
            window.remove_transition('allocation');

        this._windows.delete(window);
        this._sortedWindows.splice(this._sortedWindows.indexOf(window), 1);

        // The layout might be frozen and we might not update the windowSlots
        // on the next allocation, so remove the slot now already
        const index = this._windowSlots.findIndex(s => s[4] === window);
        if (index !== -1)
            this._windowSlots.splice(index, 1);

        // The window might have been reparented by DND
        if (window.get_parent() === this._container)
            this._container.remove_child(window);

        this._needsLayout = true;
        this.layout_changed();
    }

    syncStacking(stackIndices) {
        const windows = [...this._windows.keys()];
        windows.sort((a, b) => {
            const seqA = this._windows.get(a).metaWindow.get_stable_sequence();
            const seqB = this._windows.get(b).metaWindow.get_stable_sequence();

            return stackIndices[seqA] - stackIndices[seqB];
        });

        let lastWindow = null;
        for (const window of windows) {
            window.setStackAbove(lastWindow);
            lastWindow = window;
        }

        this._needsLayout = true;
        this.layout_changed();
    }

    /**
     * getFocusChain:
     *
     * Gets the focus chain of the workspace. This function will return
     * an empty array if the floating window layout is used.
     *
     * @returns {Array} an array of {Clutter.Actor}s
     */
    getFocusChain() {
        if (this._stateAdjustment.value === 0)
            return [];

        // The fifth element in the slot array is the WindowPreview
        return this._windowSlots.map(s => s[4]);
    }

    /**
     * An StAdjustment for controlling and transitioning between
     * the alignment of windows using the layout strategy and the
     * floating window layout.
     *
     * A value of 0 of the adjustment completely uses the floating
     * window layout while a value of 1 completely aligns windows using
     * the layout strategy.
     *
     * @type {St.Adjustment}
     */
    get stateAdjustment() {
        return this._stateAdjustment;
    }

    get spacing() {
        return this._spacing;
    }

    set spacing(s) {
        if (this._spacing === s)
            return;

        this._spacing = s;

        this._needsLayout = true;
        this.notify('spacing');
        this.layout_changed();
    }

    get layoutFrozen() {
        return this._layoutFrozen;
    }

    set layoutFrozen(f) {
        if (this._layoutFrozen === f)
            return;

        this._layoutFrozen = f;

        this.notify('layout-frozen');
        if (!this._layoutFrozen)
            this.layout_changed();
    }
});

var WorkspaceBackground = GObject.registerClass(
class WorkspaceBackground extends St.Widget {
    _init(monitorIndex, stateAdjustment) {
        super._init({
            style_class: 'workspace-background',
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });

        this._monitorIndex = monitorIndex;
        this._workarea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);

        this._stateAdjustment = stateAdjustment;
        this._stateAdjustment.connectObject('notify::value', () => {
            this._updateBorderRadius();
            this.queue_relayout();
        }, this);

        this._bin = new Clutter.Actor({
            layout_manager: new Clutter.BinLayout(),
            clip_to_allocation: true,
        });

        this._backgroundGroup = new Meta.BackgroundGroup({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });
        this._bin.add_child(this._backgroundGroup);
        this.add_child(this._bin);

        this._bgManager = new Background.BackgroundManager({
            container: this._backgroundGroup,
            monitorIndex: this._monitorIndex,
            controlPosition: false,
            useContentSize: false,
        });

        global.display.connectObject('workareas-changed', () => {
            this._workarea = Main.layoutManager.getWorkAreaForMonitor(monitorIndex);
            this._updateRoundedClipBounds();
            this.queue_relayout();
        }, this);
        this._updateRoundedClipBounds();

        this._updateBorderRadius();

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

    _updateBorderRadius() {
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        const cornerRadius = scaleFactor * BACKGROUND_CORNER_RADIUS_PIXELS;

        const backgroundContent = this._bgManager.backgroundActor.content;
        backgroundContent.rounded_clip_radius =
            Util.lerp(0, cornerRadius, this._stateAdjustment.value);
    }

    _updateRoundedClipBounds() {
        const monitor = Main.layoutManager.monitors[this._monitorIndex];

        const rect = new Graphene.Rect();
        rect.origin.x = this._workarea.x - monitor.x;
        rect.origin.y = this._workarea.y - monitor.y;
        rect.size.width = this._workarea.width;
        rect.size.height = this._workarea.height;

        this._bgManager.backgroundActor.content.set_rounded_clip_bounds(rect);
    }

    vfunc_allocate(box) {
        const [width, height] = box.get_size();
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        const scaledHeight = height - (BACKGROUND_MARGIN * 2 * scaleFactor);
        const scaledWidth = (scaledHeight / height) * width;

        const scaledBox = box.copy();
        scaledBox.set_origin(
            box.x1 + (width - scaledWidth) / 2,
            box.y1 + (height - scaledHeight) / 2);
        scaledBox.set_size(scaledWidth, scaledHeight);

        const progress = this._stateAdjustment.value;

        if (progress === 1)
            box = scaledBox;
        else if (progress !== 0)
            box = box.interpolate(scaledBox, progress);

        this.set_allocation(box);

        const themeNode = this.get_theme_node();
        const contentBox = themeNode.get_content_box(box);

        this._bin.allocate(contentBox);

        const [contentWidth, contentHeight] = contentBox.get_size();
        const monitor = Main.layoutManager.monitors[this._monitorIndex];
        const [mX1, mX2] = [monitor.x, monitor.x + monitor.width];
        const [mY1, mY2] = [monitor.y, monitor.y + monitor.height];
        const [wX1, wX2] = [this._workarea.x, this._workarea.x + this._workarea.width];
        const [wY1, wY2] = [this._workarea.y, this._workarea.y + this._workarea.height];
        const xScale = contentWidth / this._workarea.width;
        const yScale = contentHeight / this._workarea.height;
        const leftOffset = wX1 - mX1;
        const topOffset = wY1 - mY1;
        const rightOffset = mX2 - wX2;
        const bottomOffset = mY2 - wY2;

        contentBox.set_origin(-leftOffset * xScale, -topOffset * yScale);
        contentBox.set_size(
            contentWidth + (leftOffset + rightOffset) * xScale,
            contentHeight + (topOffset + bottomOffset) * yScale);
        this._backgroundGroup.allocate(contentBox);
    }

    _onDestroy() {
        if (this._bgManager) {
            this._bgManager.destroy();
            this._bgManager = null;
        }
    }
});

/**
 * @metaWorkspace: a #Meta.Workspace, or null
 */
var Workspace = GObject.registerClass(
class Workspace extends St.Widget {
    _init(metaWorkspace, monitorIndex, overviewAdjustment) {
        super._init({
            style_class: 'window-picker',
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
            layout_manager: new Clutter.BinLayout(),
        });

        const layoutManager = new WorkspaceLayout(metaWorkspace, monitorIndex,
            overviewAdjustment);

        // Background
        this._background =
            new WorkspaceBackground(monitorIndex, layoutManager.stateAdjustment);
        this.add_child(this._background);

        // Window previews
        this._container = new Clutter.Actor({
            reactive: true,
            x_expand: true,
            y_expand: true,
        });
        this._container.layout_manager = layoutManager;
        this.add_child(this._container);

        this.metaWorkspace = metaWorkspace;

        this._overviewAdjustment = overviewAdjustment;

        this.monitorIndex = monitorIndex;
        this._monitor = Main.layoutManager.monitors[this.monitorIndex];

        if (monitorIndex != Main.layoutManager.primaryIndex)
            this.add_style_class_name('external-monitor');

        const clickAction = new Clutter.ClickAction();
        clickAction.connect('clicked', action => {
            // Switch to the workspace when not the active one, leave the
            // overview otherwise.
            if (action.get_button() === 1 || action.get_button() === 0) {
                const leaveOverview = this._shouldLeaveOverview();

                this.metaWorkspace?.activate(global.get_current_time());
                if (leaveOverview)
                    Main.overview.hide();
            }
        });
        this.bind_property('mapped', clickAction, 'enabled', GObject.BindingFlags.SYNC_CREATE);
        this._container.add_action(clickAction);

        this.connect('style-changed', this._onStyleChanged.bind(this));
        this.connect('destroy', this._onDestroy.bind(this));

        this._skipTaskbarSignals = new Map();
        const windows = global.get_window_actors().map(a => a.meta_window)
            .filter(this._isMyWindow, this);

        // Create clones for windows that should be
        // visible in the Overview
        this._windows = [];
        for (let i = 0; i < windows.length; i++) {
            if (this._isOverviewWindow(windows[i]))
                this._addWindowClone(windows[i]);
        }

        // Track window changes, but let the window tracker process them first
        this.metaWorkspace?.connectObject(
            'window-added', this._windowAdded.bind(this), GObject.ConnectFlags.AFTER,
            'window-removed', this._windowRemoved.bind(this), GObject.ConnectFlags.AFTER,
            'notify::active', () => layoutManager.syncOverlays(), this);
        global.display.connectObject(
            'window-entered-monitor', this._windowEnteredMonitor.bind(this), GObject.ConnectFlags.AFTER,
            'window-left-monitor', this._windowLeftMonitor.bind(this), GObject.ConnectFlags.AFTER,
            this);
        this._layoutFrozenId = 0;

        // DND requires this to be set
        this._delegate = this;
    }

    _shouldLeaveOverview() {
        if (!this.metaWorkspace || this.metaWorkspace.active)
            return true;

        const overviewState = this._overviewAdjustment.value;
        return overviewState > OverviewControls.ControlsState.WINDOW_PICKER;
    }

    vfunc_get_focus_chain() {
        return this._container.layout_manager.getFocusChain();
    }

    _lookupIndex(metaWindow) {
        return this._windows.findIndex(w => w.metaWindow == metaWindow);
    }

    containsMetaWindow(metaWindow) {
        return this._lookupIndex(metaWindow) >= 0;
    }

    isEmpty() {
        return this._windows.length == 0;
    }

    syncStacking(stackIndices) {
        this._container.layout_manager.syncStacking(stackIndices);
    }

    _doRemoveWindow(metaWin) {
        let clone = this._removeWindowClone(metaWin);

        if (!clone)
            return;

        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
        this._container.layout_manager.layout_frozen = true;

        if (this._layoutFrozenId > 0) {
            GLib.source_remove(this._layoutFrozenId);
            this._layoutFrozenId = 0;
        }

        let [oldX, oldY] = global.get_pointer();

        this._layoutFrozenId = GLib.timeout_add(
            GLib.PRIORITY_DEFAULT,
            WINDOW_REPOSITIONING_DELAY,
            () => {
                const [newX, newY] = global.get_pointer();
                const pointerHasMoved = oldX !== newX || oldY !== newY;
                const actorUnderPointer = global.stage.get_actor_at_pos(
                    Clutter.PickMode.REACTIVE, newX, newY);

                if ((pointerHasMoved && this.contains(actorUnderPointer)) ||
                    this._windows.some(w => w.contains(actorUnderPointer))) {
                    oldX = newX;
                    oldY = newY;
                    return GLib.SOURCE_CONTINUE;
                }

                this._container.layout_manager.layout_frozen = false;
                this._layoutFrozenId = 0;
                return GLib.SOURCE_REMOVE;
            });

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

    _doAddWindow(metaWin) {
        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 = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                if (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(metaWin))
            return;

        this._skipTaskbarSignals.set(metaWin,
            metaWin.connect('notify::skip-taskbar', () => {
                if (metaWin.skip_taskbar)
                    this._doRemoveWindow(metaWin);
                else
                    this._doAddWindow(metaWin);
            }));

        if (!this._isOverviewWindow(metaWin)) {
            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;
        }

        const clone = this._addWindowClone(metaWin);

        clone.set_pivot_point(0.5, 0.5);
        clone.scale_x = 0;
        clone.scale_y = 0;
        clone.ease({
            scale_x: 1,
            scale_y: 1,
            duration: 250,
            onStopped: () => clone.set_pivot_point(0, 0),
        });

        if (this._layoutFrozenId > 0) {
            // If a window was closed before, unfreeze the layout to ensure
            // the new window is immediately shown
            this._container.layout_manager.layout_frozen = false;

            GLib.source_remove(this._layoutFrozenId);
            this._layoutFrozenId = 0;
        }
    }

    _windowAdded(metaWorkspace, metaWin) {
        if (!Main.overview.closing)
            this._doAddWindow(metaWin);
    }

    _windowRemoved(metaWorkspace, metaWin) {
        this._doRemoveWindow(metaWin);
    }

    _windowEnteredMonitor(metaDisplay, monitorIndex, metaWin) {
        if (monitorIndex === this.monitorIndex && !Main.overview.closing)
            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;
    }

    _clearSkipTaskbarSignals() {
        for (const [metaWin, id] of this._skipTaskbarSignals)
            metaWin.disconnect(id);
        this._skipTaskbarSignals.clear();
    }

    prepareToLeaveOverview() {
        this._clearSkipTaskbarSignals();

        for (let i = 0; i < this._windows.length; i++)
            this._windows[i].remove_all_transitions();

        if (this._layoutFrozenId > 0) {
            GLib.source_remove(this._layoutFrozenId);
            this._layoutFrozenId = 0;
        }

        this._container.layout_manager.layout_frozen = true;
        Main.overview.connectObject(
            'hidden', this._doneLeavingOverview.bind(this), this);
    }

    _onDestroy() {
        this._clearSkipTaskbarSignals();

        if (this._layoutFrozenId > 0) {
            GLib.source_remove(this._layoutFrozenId);
            this._layoutFrozenId = 0;
        }

        this._windows = [];
    }

    _doneLeavingOverview() {
        this._container.layout_manager.layout_frozen = false;
    }

    _doneShowingOverview() {
        this._container.layout_manager.layout_frozen = false;
    }

    _isMyWindow(window) {
        const isOnWorkspace = this.metaWorkspace === null ||
            window.located_on_workspace(this.metaWorkspace);
        const isOnMonitor = window.get_monitor() === this.monitorIndex;

        return isOnWorkspace && isOnMonitor;
    }

    _isOverviewWindow(window) {
        return !window.skip_taskbar;
    }

    // Create a clone of a (non-desktop) window and add it to the window list
    _addWindowClone(metaWindow) {
        let clone = new WindowPreview(metaWindow, this, this._overviewAdjustment);

        clone.connect('selected',
                      this._onCloneSelected.bind(this));
        clone.connect('drag-begin', () => {
            Main.overview.beginWindowDrag(metaWindow);
        });
        clone.connect('drag-cancelled', () => {
            Main.overview.cancelledWindowDrag(metaWindow);
        });
        clone.connect('drag-end', () => {
            Main.overview.endWindowDrag(metaWindow);
        });
        clone.connect('show-chrome', () => {
            let focus = global.stage.key_focus;
            if (focus == null || this.contains(focus))
                clone.grab_key_focus();

            this._windows.forEach(c => {
                if (c !== clone)
                    c.hideOverlay(true);
            });
        });
        clone.connect('destroy', () => {
            this._doRemoveWindow(metaWindow);
        });

        this._container.layout_manager.addWindow(clone, metaWindow);

        if (this._windows.length == 0)
            clone.setStackAbove(null);
        else
            clone.setStackAbove(this._windows[this._windows.length - 1]);

        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;

        this._container.layout_manager.removeWindow(this._windows[index]);

        return this._windows.splice(index, 1).pop();
    }

    _onStyleChanged() {
        const themeNode = this.get_theme_node();
        this._container.layout_manager.spacing = themeNode.get_length('spacing');
    }

    _onCloneSelected(clone, time) {
        const wsIndex = this.metaWorkspace?.index();

        if (this._shouldLeaveOverview())
            Main.activateWindow(clone.metaWindow, time, wsIndex);
        else
            this.metaWorkspace?.activate(time);
    }

    // Draggable target interface
    handleDragOver(source, _actor, _x, _y, _time) {
        if (source.metaWindow && !this._isMyWindow(source.metaWindow))
            return DND.DragMotionResult.MOVE_DROP;
        if (source.app && source.app.can_open_new_window())
            return DND.DragMotionResult.COPY_DROP;
        if (!source.app && source.shellWorkspaceLaunch)
            return DND.DragMotionResult.COPY_DROP;

        return DND.DragMotionResult.CONTINUE;
    }

    acceptDrop(source, actor, x, y, time) {
        let workspaceManager = global.workspace_manager;
        let workspaceIndex = this.metaWorkspace
            ? this.metaWorkspace.index()
            : workspaceManager.get_active_workspace_index();

        if (source.metaWindow) {
            const window = source.metaWindow;
            if (this._isMyWindow(window))
                return false;

            Main.moveWindowToMonitorAndWorkspace(window,
                this.monitorIndex, workspaceIndex);
            return true;
        } else if (source.app && source.app.can_open_new_window()) {
            if (source.animateLaunchAtPos)
                source.animateLaunchAtPos(actor.x, actor.y);

            source.app.open_new_window(workspaceIndex);
            return true;
        } else if (!source.app && source.shellWorkspaceLaunch) {
            // While unused in our own drag sources, shellWorkspaceLaunch allows
            // extensions to define custom actions for their drag sources.
            source.shellWorkspaceLaunch({
                workspace: workspaceIndex,
                timestamp: time,
            });
            return true;
        }

        return false;
    }

    get stateAdjustment() {
        return this._container.layout_manager.stateAdjustment;
    }
});
(uuay)userWidget.js!// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
//
// A widget showing the user avatar and name
/* exported UserWidget */

const { Clutter, 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 = GObject.registerClass(
class Avatar extends St.Bin {
    _init(user, params) {
        let themeContext = St.ThemeContext.get_for_stage(global.stage);
        params = Params.parse(params, {
            styleClass: 'user-icon',
            reactive: false,
            iconSize: AVATAR_ICON_SIZE,
        });

        super._init({
            style_class: params.styleClass,
            reactive: params.reactive,
            width: params.iconSize * themeContext.scaleFactor,
            height: params.iconSize * themeContext.scaleFactor,
        });

        this._iconSize = params.iconSize;
        this._user = user;

        this.bind_property('reactive', this, 'track-hover',
            GObject.BindingFlags.SYNC_CREATE);
        this.bind_property('reactive', this, 'can-focus',
            GObject.BindingFlags.SYNC_CREATE);

        // Monitor the scaling factor to make sure we recreate the avatar when needed.
        themeContext.connectObject('notify::scale-factor', this.update.bind(this), this);
    }

    vfunc_style_changed() {
        super.vfunc_style_changed();

        let node = this.get_theme_node();
        let [found, iconSize] = node.lookup_length('icon-size', false);

        if (!found)
            return;

        let themeContext = St.ThemeContext.get_for_stage(global.stage);

        // node.lookup_length() returns a scaled value, but we
        // need unscaled
        this._iconSize = iconSize / themeContext.scaleFactor;
        this.update();
    }

    setSensitive(sensitive) {
        this.reactive = sensitive;
    }

    update() {
        let iconFile = null;
        if (this._user) {
            iconFile = this._user.get_icon_file();
            if (iconFile && !GLib.file_test(iconFile, GLib.FileTest.EXISTS))
                iconFile = null;
        }

        let { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        this.set_size(
            this._iconSize * scaleFactor,
            this._iconSize * scaleFactor);

        if (iconFile) {
            this.child = null;
            this.add_style_class_name('user-avatar');
            this.style = `
                background-image: url("${iconFile}");
                background-size: cover;`;
        } else {
            this.style = null;
            this.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._user.connectObject(
            'notify::is-loaded', this._updateUser.bind(this),
            'changed', this._updateUser.bind(this), this);
        this._updateUser();
    }

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

        let availWidth = box.x2 - box.x1;
        let availHeight = box.y2 - box.y1;

        let [, , natRealNameWidth] = this._realNameLabel.get_preferred_size();

        let childBox = new Clutter.ActorBox();

        let hiddenLabel;
        if (natRealNameWidth <= availWidth) {
            this._currentLabel = this._realNameLabel;
            hiddenLabel = this._userNameLabel;
        } else {
            this._currentLabel = this._userNameLabel;
            hiddenLabel = this._realNameLabel;
        }
        this.label_actor = this._currentLabel;

        hiddenLabel.allocate(childBox);

        childBox.set_size(availWidth, availHeight);

        this._currentLabel.allocate(childBox);
    }

    vfunc_paint(paintContext) {
        this._currentLabel.paint(paintContext);
    }

    _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 = GObject.registerClass(
class UserWidget extends St.BoxLayout {
    _init(user, orientation = Clutter.Orientation.HORIZONTAL) {
        // If user is null, that implies a username-based login authorization.
        this._user = user;

        let vertical = orientation == Clutter.Orientation.VERTICAL;
        let xAlign = vertical ? Clutter.ActorAlign.CENTER : Clutter.ActorAlign.START;
        let styleClass = vertical ? 'user-widget vertical' : 'user-widget horizontal';

        super._init({
            styleClass,
            vertical,
            xAlign,
        });

        this._avatar = new Avatar(user);
        this._avatar.x_align = Clutter.ActorAlign.CENTER;
        this.add_child(this._avatar);

        this._userLoadedId = 0;
        this._userChangedId = 0;
        if (user) {
            this._label = new UserWidgetLabel(user);
            this.add_child(this._label);

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

            this._user.connectObject(
                'notify::is-loaded', this._updateUser.bind(this),
                'changed', this._updateUser.bind(this), this);
        } else {
            this._label = new St.Label({
                style_class: 'user-widget-label',
                text: 'Empty User',
                opacity: 0,
            });
            this.add_child(this._label);
        }

        this._updateUser();
    }

    _updateUser() {
        this._avatar.update();
    }
});
(uuay)volume.js6// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Clutter, Gio, GLib, GObject, 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';

const VolumeType = {
    OUTPUT: 0,
    INPUT: 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.hide();

        this._inDrag = false;
        this._notifyVolumeChangeId = 0;

        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._sliderChangedId = this._slider.connect('notify::value',
                                                     this._sliderChanged.bind(this));
        this._slider.connect('drag-begin', () => (this._inDrag = true));
        this._slider.connect('drag-end', () => {
            this._inDrag = false;
            this._notifyVolumeChange();
        });

        this._icon = new St.Icon({ style_class: 'popup-menu-icon' });
        this.item.add(this._icon);
        this.item.add_child(this._slider);
        this.item.connect('button-press-event', (actor, event) => {
            return this._slider.startDragging(event);
        });
        this.item.connect('key-press-event', (actor, event) => {
            return this._slider.emit('key-press-event', event);
        });
        this.item.connect('scroll-event', (actor, event) => {
            return this._slider.emit('scroll-event', event);
        });

        this._stream = null;
        this._volumeCancellable = null;
        this._icons = [];
    }

    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.disconnectObject(this);
    }

    _connectStream(stream) {
        stream.connectObject(
            'notify::is-muted', this._updateVolume.bind(this),
            'notify::volume', this._updateVolume.bind(this), this);
    }

    _shouldBeVisible() {
        return this._stream != null;
    }

    _updateVisibility() {
        let visible = this._shouldBeVisible();
        this.item.visible = visible;
    }

    scroll(event) {
        return this._slider.scroll(event);
    }

    _sliderChanged() {
        if (!this._stream)
            return;

        let value = this._slider.value;
        let volume = value * this._control.get_vol_max_norm();
        let prevMuted = this._stream.is_muted;
        let prevVolume = this._stream.volume;
        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();

        let volumeChanged = this._stream.volume !== prevVolume;
        if (volumeChanged && !this._notifyVolumeChangeId && !this._inDrag) {
            this._notifyVolumeChangeId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 30, () => {
                this._notifyVolumeChange();
                this._notifyVolumeChangeId = 0;
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(this._notifyVolumeChangeId,
                '[gnome-shell] this._notifyVolumeChangeId');
        }
    }

    _notifyVolumeChange() {
        if (this._volumeCancellable)
            this._volumeCancellable.cancel();
        this._volumeCancellable = null;

        if (this._stream.state === Gvc.MixerStreamState.RUNNING)
            return; // feedback not necessary while playing

        this._volumeCancellable = new Gio.Cancellable();
        let player = global.display.get_sound_player();
        player.play_from_theme('audio-volume-change',
                               _("Volume changed"),
                               this._volumeCancellable);
    }

    _changeSlider(value) {
        this._slider.block_signal_handler(this._sliderChangedId);
        this._slider.value = value;
        this._slider.unblock_signal_handler(this._sliderChangedId);
    }

    _updateVolume() {
        let muted = this._stream.is_muted;
        this._changeSlider(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);

        this._slider.maximum_value = this._allowAmplified
            ? this.getMaxLevel() : 1;

        if (this._stream)
            this._updateVolume();
    }

    getIcon() {
        if (!this._stream)
            return null;

        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());
            n = Math.clamp(n, 1, this._icons.length - 1);
        }
        return this._icons[n];
    }

    getLevel() {
        if (!this._stream)
            return null;

        return 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 maxVolume / this._control.get_vol_max_norm();
    }
};
Signals.addSignalMethods(StreamSlider.prototype);

var OutputStreamSlider = class extends StreamSlider {
    constructor(control) {
        super(control);
        this._slider.accessible_name = _("Volume");
        this._icons = [
            'audio-volume-muted-symbolic',
            'audio-volume-low-symbolic',
            'audio-volume-medium-symbolic',
            'audio-volume-high-symbolic',
            'audio-volume-overamplified-symbolic',
        ];
    }

    _connectStream(stream) {
        super._connectStream(stream);
        stream.connectObject('notify::port',
            this._portChanged.bind(this), 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.includes('headphone');

        return false;
    }

    _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.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';
        this._icons = [
            'microphone-sensitivity-muted-symbolic',
            'microphone-sensitivity-low-symbolic',
            'microphone-sensitivity-medium-symbolic',
            'microphone-sensitivity-high-symbolic',
        ];
    }

    _connectStream(stream) {
        super._connectStream(stream);
        this._maybeShowInput();
    }

    _maybeShowInput() {
        // only show input widgets if any application is recording audio
        let showInput = false;
        if (this._stream) {
            // skip gnome-volume-control and pavucontrol which appear
            // as recording because they show the input level
            let skippedApps = [
                'org.gnome.VolumeControl',
                'org.PulseAudio.pavucontrol',
            ];

            showInput = this._control.get_source_outputs().some(output => {
                return !skippedApps.includes(output.get_application_id());
            });
        }

        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('output-icon-changed');
        });
        this.addMenuItem(this._output.item);

        this._input = new InputStreamSlider(this._control);
        this._input.item.connect('notify::visible', () => {
            this.emit('input-visible-changed');
        });
        this._input.connect('stream-updated', () => {
            this.emit('input-icon-changed');
        });
        this.addMenuItem(this._input.item);

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

        this._onControlStateChanged();
    }

    scroll(type, event) {
        return type === VolumeType.INPUT
            ? this._input.scroll(event)
            : this._output.scroll(event);
    }

    _onControlStateChanged() {
        if (this._control.get_state() == Gvc.MixerControlState.READY) {
            this._readInput();
            this._readOutput();
        } else {
            this.emit('output-icon-changed');
        }
    }

    _readOutput() {
        this._output.stream = this._control.get_default_sink();
    }

    _readInput() {
        this._input.stream = this._control.get_default_source();
    }

    getIcon(type) {
        return type === VolumeType.INPUT
            ? this._input.getIcon()
            : this._output.getIcon();
    }

    getLevel(type) {
        return type === VolumeType.INPUT
            ? this._input.getLevel()
            : this._output.getLevel();
    }

    getMaxLevel(type) {
        return type === VolumeType.INPUT
            ? this._input.getMaxLevel()
            : this._output.getMaxLevel();
    }

    getInputVisible() {
        return this._input.item.visible;
    }
};

var Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        this._primaryIndicator = this._addIndicator();
        this._inputIndicator = this._addIndicator();

        this._primaryIndicator.reactive = true;
        this._inputIndicator.reactive = true;

        this._primaryIndicator.connect('scroll-event',
            (actor, event) => this._handleScrollEvent(VolumeType.OUTPUT, event));
        this._inputIndicator.connect('scroll-event',
            (actor, event) => this._handleScrollEvent(VolumeType.INPUT, event));

        this._control = getMixerControl();
        this._volumeMenu = new VolumeMenu(this._control);
        this._volumeMenu.connect('output-icon-changed', () => {
            let icon = this._volumeMenu.getIcon(VolumeType.OUTPUT);

            if (icon != null)
                this._primaryIndicator.icon_name = icon;
            this._primaryIndicator.visible = icon !== null;
        });

        this._inputIndicator.visible = this._volumeMenu.getInputVisible();
        this._volumeMenu.connect('input-visible-changed', () => {
            this._inputIndicator.visible = this._volumeMenu.getInputVisible();
        });
        this._volumeMenu.connect('input-icon-changed', () => {
            let icon = this._volumeMenu.getIcon(VolumeType.INPUT);

            if (icon !== null)
                this._inputIndicator.icon_name = icon;
        });

        this.menu.addMenuItem(this._volumeMenu);
    }

    _handleScrollEvent(type, event) {
        const result = this._volumeMenu.scroll(type, event);
        if (result == Clutter.EVENT_PROPAGATE || this.menu.actor.mapped)
            return result;

        const gicon = new Gio.ThemedIcon({ name: this._volumeMenu.getIcon(type) });
        const level = this._volumeMenu.getLevel(type);
        const maxLevel = this._volumeMenu.getMaxLevel(type);
        Main.osdWindowManager.show(-1, gicon, null, level, maxLevel);
        return result;
    }
});
(uuay)checkBox.jsn/* exported CheckBox */
const { Atk, Clutter, GObject, Pango, St } = imports.gi;

var CheckBox = GObject.registerClass(
class CheckBox extends St.Button {
    _init(label) {
        let container = new St.BoxLayout({
            x_expand: true,
            y_expand: true,
        });
        super._init({
            style_class: 'check-box',
            child: container,
            button_mask: St.ButtonMask.ONE,
            toggle_mode: true,
            can_focus: true,
        });
        this.set_accessible_role(Atk.Role.CHECK_BOX);

        this._box = new St.Bin({ y_align: Clutter.ActorAlign.START });
        container.add_actor(this._box);

        this._label = new St.Label({ y_align: Clutter.ActorAlign.CENTER });
        this._label.clutter_text.set_line_wrap(true);
        this._label.clutter_text.set_ellipsize(Pango.EllipsizeMode.NONE);
        this.set_label_actor(this._label);
        container.add_actor(this._label);

        if (label)
            this.setLabel(label);
    }

    setLabel(label) {
        this._label.set_text(label);
    }

    getLabelActor() {
        return this._label;
    }
});
(uuay)appDisplay.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported AppDisplay, AppSearchProvider */

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

const AppFavorites = imports.ui.appFavorites;
const { AppMenu } = imports.ui.appMenu;
const BoxPointer = imports.ui.boxpointer;
const DND = imports.ui.dnd;
const GrabHelper = imports.ui.grabHelper;
const IconGrid = imports.ui.iconGrid;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const PageIndicators = imports.ui.pageIndicators;
const ParentalControlsManager = imports.misc.parentalControlsManager;
const PopupMenu = imports.ui.popupMenu;
const Search = imports.ui.search;
const SwipeTracker = imports.ui.swipeTracker;
const Params = imports.misc.params;
const SystemActions = imports.misc.systemActions;

var MENU_POPUP_TIMEOUT = 600;
var POPDOWN_DIALOG_TIMEOUT = 500;

var FOLDER_SUBICON_FRACTION = .4;

var VIEWS_SWITCH_TIME = 400;
var VIEWS_SWITCH_ANIMATION_DELAY = 100;

var SCROLL_TIMEOUT_TIME = 150;

var APP_ICON_SCALE_IN_TIME = 500;
var APP_ICON_SCALE_IN_DELAY = 700;

var APP_ICON_TITLE_EXPAND_TIME = 200;
var APP_ICON_TITLE_COLLAPSE_TIME = 100;

const FOLDER_DIALOG_ANIMATION_TIME = 200;

const PAGE_PREVIEW_ANIMATION_TIME = 150;
const PAGE_PREVIEW_ANIMATION_START_OFFSET = 100;
const PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET = 300;
const PAGE_PREVIEW_MAX_ARROW_OFFSET = 80;
const PAGE_INDICATOR_FADE_TIME = 200;
const MAX_PAGE_PADDING = 200;

const OVERSHOOT_THRESHOLD = 20;
const OVERSHOOT_TIMEOUT = 1000;

const DELAYED_MOVE_TIMEOUT = 200;

const DIALOG_SHADE_NORMAL = Clutter.Color.from_pixel(0x000000cc);
const DIALOG_SHADE_HIGHLIGHT = Clutter.Color.from_pixel(0x00000055);

const DEFAULT_FOLDERS = {
    'Utilities': {
        name: 'X-GNOME-Utilities.directory',
        categories: ['X-GNOME-Utilities'],
        apps: [
            'gnome-abrt.desktop',
            'gnome-system-log.desktop',
            'nm-connection-editor.desktop',
            'org.gnome.baobab.desktop',
            'org.gnome.Connections.desktop',
            'org.gnome.DejaDup.desktop',
            'org.gnome.Dictionary.desktop',
            'org.gnome.DiskUtility.desktop',
            'org.gnome.eog.desktop',
            'org.gnome.Evince.desktop',
            'org.gnome.FileRoller.desktop',
            'org.gnome.fonts.desktop',
            'org.gnome.seahorse.Application.desktop',
            'org.gnome.tweaks.desktop',
            'org.gnome.Usage.desktop',
            'vinagre.desktop',
        ],
    },
    'YaST': {
        name: 'suse-yast.directory',
        categories: ['X-SuSE-YaST'],
    },
};

var SidePages = {
    NONE: 0,
    PREVIOUS: 1 << 0,
    NEXT: 1 << 1,
    DND: 1 << 2,
};

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.includes(itemA))
            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 _getViewFromIcon(icon) {
    for (let parent = icon.get_parent(); parent; parent = parent.get_parent()) {
        if (parent instanceof BaseAppView)
            return parent;
    }
    return null;
}

function _findBestFolderName(apps) {
    let appInfos = apps.map(app => app.get_app_info());

    let categoryCounter = {};
    let commonCategories = [];

    appInfos.reduce((categories, appInfo) => {
        for (let category of _getCategories(appInfo)) {
            if (!(category in categoryCounter))
                categoryCounter[category] = 0;

            categoryCounter[category] += 1;

            // If a category is present in all apps, its counter will
            // reach appInfos.length
            if (category.length > 0 &&
                categoryCounter[category] == appInfos.length)
                categories.push(category);
        }
        return categories;
    }, commonCategories);

    for (let category of commonCategories) {
        const directory = `${category}.directory`;
        const translated = Shell.util_get_translated_folder_name(directory);
        if (translated !== null)
            return translated;
    }

    return null;
}

var BaseAppView = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
    Properties: {
        'gesture-modes': GObject.ParamSpec.flags(
            'gesture-modes', 'gesture-modes', 'gesture-modes',
            GObject.ParamFlags.READWRITE | GObject.ParamFlags.CONSTRUCT_ONLY,
            Shell.ActionMode, Shell.ActionMode.OVERVIEW),
    },
    Signals: {
        'view-loaded': {},
    },
}, class BaseAppView extends St.Widget {
    _init(params = {}) {
        super._init(params);

        this._grid = this._createGrid();
        this._grid._delegate = this;
        // Standard hack for ClutterBinLayout
        this._grid.x_expand = true;
        this._grid.connect('pages-changed', () => {
            this.goToPage(this._grid.currentPage);
            this._pageIndicators.setNPages(this._grid.nPages);
            this._pageIndicators.setCurrentPosition(this._grid.currentPage);
        });

        // Scroll View
        this._scrollView = new St.ScrollView({
            style_class: 'apps-scroll-view',
            clip_to_allocation: true,
            x_expand: true,
            y_expand: true,
            reactive: true,
            enable_mouse_scrolling: false,
        });
        this._scrollView.set_policy(St.PolicyType.EXTERNAL, St.PolicyType.NEVER);
        this._scrollView._delegate = this;

        this._canScroll = true; // limiting scrolling speed
        this._scrollTimeoutId = 0;
        this._scrollView.connect('scroll-event', this._onScroll.bind(this));
        this._scrollView.connect('motion-event', this._onMotion.bind(this));
        this._scrollView.connect('enter-event', this._onMotion.bind(this));
        this._scrollView.connect('leave-event', this._onLeave.bind(this));
        this._scrollView.connect('button-press-event', this._onButtonPress.bind(this));

        this._scrollView.add_actor(this._grid);

        const scroll = this._scrollView.hscroll;
        this._adjustment = scroll.adjustment;
        this._adjustment.connect('notify::value', adj => {
            this._updateFade();
            const value = adj.value / adj.page_size;
            this._pageIndicators.setCurrentPosition(value);

            const distanceToPage = Math.abs(Math.round(value) - value);
            if (distanceToPage < 0.001) {
                this._hintContainer.opacity = 255;
                this._hintContainer.translationX = 0;
            } else {
                this._hintContainer.remove_transition('opacity');
                let opacity = Math.clamp(
                    255 * (1 - (distanceToPage * 2)),
                    0, 255);

                this._hintContainer.translationX = (Math.round(value) - value) * adj.page_size;
                this._hintContainer.opacity = opacity;
            }
        });

        // Page Indicators
        this._pageIndicators =
            new PageIndicators.PageIndicators(Clutter.Orientation.HORIZONTAL);

        this._pageIndicators.y_expand = false;
        this._pageIndicators.connect('page-activated',
            (indicators, pageIndex) => {
                this.goToPage(pageIndex);
            });
        this._pageIndicators.connect('scroll-event', (actor, event) => {
            this._scrollView.event(event, false);
        });

        // Navigation indicators
        this._nextPageIndicator = new St.Widget({
            style_class: 'page-navigation-hint next',
            opacity: 0,
            visible: false,
            reactive: false,
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.END,
            y_align: Clutter.ActorAlign.FILL,
        });

        this._prevPageIndicator = new St.Widget({
            style_class: 'page-navigation-hint previous',
            opacity: 0,
            visible: false,
            reactive: false,
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.FILL,
        });

        // Next/prev page arrows
        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
        this._nextPageArrow = new St.Icon({
            style_class: 'page-navigation-arrow',
            icon_name: rtl
                ? 'carousel-arrow-previous-symbolic'
                : 'carousel-arrow-next-symbolic',
            opacity: 0,
            reactive: false,
            visible: false,
            x_expand: true,
            x_align: Clutter.ActorAlign.END,
        });
        this._prevPageArrow = new St.Icon({
            style_class: 'page-navigation-arrow',
            icon_name: rtl
                ? 'carousel-arrow-next-symbolic'
                : 'carousel-arrow-previous-symbolic',
            opacity: 0,
            reactive: false,
            visible: false,
            x_expand: true,
            x_align: Clutter.ActorAlign.START,
        });

        this._hintContainer = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });
        this._hintContainer.add_child(this._prevPageIndicator);
        this._hintContainer.add_child(this._nextPageIndicator);

        const scrollContainer = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            clip_to_allocation: true,
            y_expand: true,
        });
        scrollContainer.add_child(this._hintContainer);
        scrollContainer.add_child(this._scrollView);
        scrollContainer.add_child(this._nextPageArrow);
        scrollContainer.add_child(this._prevPageArrow);

        this._box = new St.BoxLayout({
            vertical: true,
            x_expand: true,
            y_expand: true,
        });
        this._box.add_child(scrollContainer);
        this._box.add_child(this._pageIndicators);

        // Swipe
        this._swipeTracker = new SwipeTracker.SwipeTracker(this._scrollView,
            Clutter.Orientation.HORIZONTAL, this.gestureModes);
        this._swipeTracker.orientation = Clutter.Orientation.HORIZONTAL;
        this._swipeTracker.connect('begin', this._swipeBegin.bind(this));
        this._swipeTracker.connect('update', this._swipeUpdate.bind(this));
        this._swipeTracker.connect('end', this._swipeEnd.bind(this));

        this._availWidth = 0;
        this._availHeight = 0;
        this._orientation = Clutter.Orientation.HORIZONTAL;

        this._items = new Map();
        this._orderedItems = [];

        // Filter the apps through the user’s parental controls.
        this._parentalControlsManager = ParentalControlsManager.getDefault();
        this._parentalControlsManager.connectObject('app-filter-changed',
            () => this._redisplay(), this);

        // Don't duplicate favorites
        this._appFavorites = AppFavorites.getAppFavorites();
        this._appFavorites.connectObject('changed',
            () => this._redisplay(), this);

        // Drag n' Drop
        this._lastOvershoot = -1;
        this._lastOvershootTimeoutId = 0;
        this._delayedMoveData = null;

        this._dragBeginId = 0;
        this._dragEndId = 0;
        this._dragCancelledId = 0;

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

        this._previewedPages = new Map();
    }

    _onDestroy() {
        if (this._swipeTracker) {
            this._swipeTracker.destroy();
            delete this._swipeTracker;
        }

        this._removeDelayedMove();
        this._disconnectDnD();
    }

    _updateFadeForNavigation() {
        const fadeMargin = new Clutter.Margin();
        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
        const showingNextPage = this._pagesShown & SidePages.NEXT;
        const showingPrevPage = this._pagesShown & SidePages.PREVIOUS;

        if ((showingNextPage && !rtl) || (showingPrevPage && rtl)) {
            fadeMargin.right = Math.max(
                -PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET,
                -(this._availWidth - this._grid.layout_manager.pageWidth) / 2);
        }

        if ((showingPrevPage && !rtl) || (showingNextPage && rtl)) {
            fadeMargin.left = Math.max(
                -PAGE_PREVIEW_FADE_EFFECT_MAX_OFFSET,
                -(this._availWidth - this._grid.layout_manager.pageWidth) / 2);
        }

        this._scrollView.update_fade_effect(fadeMargin);
        const effect = this._scrollView.get_effect('fade');
        if (effect)
            effect.extend_fade_area = true;
    }

    _updateFade() {
        const { pagePadding } = this._grid.layout_manager;

        if (this._pagesShown)
            return;

        if (pagePadding.top === 0 &&
            pagePadding.right === 0 &&
            pagePadding.bottom === 0 &&
            pagePadding.left === 0)
            return;

        let hOffset = 0;
        let vOffset = 0;

        if ((this._adjustment.value % this._adjustment.page_size) !== 0.0) {
            const vertical = this._orientation === Clutter.Orientation.VERTICAL;

            hOffset = vertical ? 0 : Math.max(pagePadding.left, pagePadding.right);
            vOffset = vertical ? Math.max(pagePadding.top, pagePadding.bottom) : 0;

            if (hOffset === 0 && vOffset === 0)
                return;
        }

        this._scrollView.update_fade_effect(
            new Clutter.Margin({
                left: hOffset,
                right: hOffset,
                top: vOffset,
                bottom: vOffset,
            }));
    }

    _createGrid() {
        return new IconGrid.IconGrid({ allow_incomplete_pages: true });
    }

    _onScroll(actor, event) {
        if (this._swipeTracker.canHandleScrollEvent(event))
            return Clutter.EVENT_PROPAGATE;

        if (!this._canScroll)
            return Clutter.EVENT_STOP;

        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
        const vertical = this._orientation === Clutter.Orientation.VERTICAL;

        let nextPage = this._grid.currentPage;
        switch (event.get_scroll_direction()) {
        case Clutter.ScrollDirection.UP:
            nextPage -= 1;
            break;

        case Clutter.ScrollDirection.DOWN:
            nextPage += 1;
            break;

        case Clutter.ScrollDirection.LEFT:
            if (vertical)
                return Clutter.EVENT_STOP;
            nextPage += rtl ? 1 : -1;
            break;

        case Clutter.ScrollDirection.RIGHT:
            if (vertical)
                return Clutter.EVENT_STOP;
            nextPage += rtl ? -1 : 1;
            break;

        default:
            return Clutter.EVENT_STOP;
        }

        this.goToPage(nextPage);

        this._canScroll = false;
        this._scrollTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
            SCROLL_TIMEOUT_TIME, () => {
                this._canScroll = true;
                this._scrollTimeoutId = 0;
                return GLib.SOURCE_REMOVE;
            });

        return Clutter.EVENT_STOP;
    }

    _pageForCoords(x, y) {
        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
        const { allocation } = this._grid;

        const [success, pointerX] = this._scrollView.transform_stage_point(x, y);
        if (!success)
            return SidePages.NONE;

        if (pointerX < allocation.x1)
            return rtl ? SidePages.NEXT : SidePages.PREVIOUS;
        else if (pointerX > allocation.x2)
            return rtl ? SidePages.PREVIOUS : SidePages.NEXT;

        return SidePages.NONE;
    }

    _onMotion(actor, event) {
        const page = this._pageForCoords(...event.get_coords());
        this._slideSidePages(page);

        return Clutter.EVENT_PROPAGATE;
    }

    _onButtonPress(actor, event) {
        const page = this._pageForCoords(...event.get_coords());
        if (page === SidePages.NEXT)
            this.goToPage(this._grid.currentPage + 1);
        else if (page === SidePages.PREVIOUS)
            this.goToPage(this._grid.currentPage - 1);
    }

    _onLeave() {
        this._slideSidePages(SidePages.NONE);
    }

    _swipeBegin(tracker, monitor) {
        if (monitor !== Main.layoutManager.primaryIndex)
            return;

        if (this._dragFocus) {
            this._dragFocus.cancelActions();
            this._dragFocus = null;
        }

        const adjustment = this._adjustment;
        adjustment.remove_transition('value');

        const progress = adjustment.value / adjustment.page_size;
        const points = Array.from({ length: this._grid.nPages }, (v, i) => i);
        const size = tracker.orientation === Clutter.Orientation.VERTICAL
            ? this._grid.allocation.get_height() : this._grid.allocation.get_width();

        tracker.confirmSwipe(size, points, progress, Math.round(progress));
    }

    _swipeUpdate(tracker, progress) {
        const adjustment = this._adjustment;
        adjustment.value = progress * adjustment.page_size;
    }

    _swipeEnd(tracker, duration, endProgress) {
        const adjustment = this._adjustment;
        const value = endProgress * adjustment.page_size;

        this._syncPageHints(endProgress);

        adjustment.ease(value, {
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            duration,
            onComplete: () => this.goToPage(endProgress, false),
        });
    }

    _connectDnD() {
        this._dragBeginId =
            Main.overview.connect('item-drag-begin', this._onDragBegin.bind(this));
        this._dragEndId =
            Main.overview.connect('item-drag-end', this._onDragEnd.bind(this));
        this._dragCancelledId =
            Main.overview.connect('item-drag-cancelled', this._onDragCancelled.bind(this));
    }

    _disconnectDnD() {
        if (this._dragBeginId > 0) {
            Main.overview.disconnect(this._dragBeginId);
            this._dragBeginId = 0;
        }

        if (this._dragEndId > 0) {
            Main.overview.disconnect(this._dragEndId);
            this._dragEndId = 0;
        }

        if (this._dragCancelledId > 0) {
            Main.overview.disconnect(this._dragCancelledId);
            this._dragCancelledId = 0;
        }

        if (this._dragMonitor) {
            DND.removeDragMonitor(this._dragMonitor);
            this._dragMonitor = null;
        }
    }

    _maybeMoveItem(dragEvent) {
        const [success, x, y] =
            this._grid.transform_stage_point(dragEvent.x, dragEvent.y);

        if (!success)
            return;

        const { source } = dragEvent;
        const [page, position, dragLocation] =
            this._getDropTarget(x, y, source);
        const item = position !== -1
            ? this._grid.getItemAt(page, position) : null;


        // Dragging over invalid parts of the grid cancels the timeout
        if (item === source ||
            dragLocation === IconGrid.DragLocation.INVALID ||
            dragLocation === IconGrid.DragLocation.ON_ICON) {
            this._removeDelayedMove();
            return;
        }

        if (!this._delayedMoveData ||
            this._delayedMoveData.page !== page ||
            this._delayedMoveData.position !== position) {
            // Update the item with a small delay
            this._removeDelayedMove();
            this._delayedMoveData = {
                page,
                position,
                source,
                destroyId: source.connect('destroy', () => this._removeDelayedMove()),
                timeoutId: GLib.timeout_add(GLib.PRIORITY_DEFAULT,
                    DELAYED_MOVE_TIMEOUT, () => {
                        this._moveItem(source, page, position);
                        this._delayedMoveData.timeoutId = 0;
                        this._removeDelayedMove();
                        return GLib.SOURCE_REMOVE;
                    }),
            };
        }
    }

    _removeDelayedMove() {
        if (!this._delayedMoveData)
            return;

        const { source, destroyId, timeoutId  } = this._delayedMoveData;

        if (timeoutId > 0)
            GLib.source_remove(timeoutId);

        if (destroyId > 0)
            source.disconnect(destroyId);

        this._delayedMoveData = null;
    }

    _resetOvershoot() {
        if (this._lastOvershootTimeoutId)
            GLib.source_remove(this._lastOvershootTimeoutId);
        this._lastOvershootTimeoutId = 0;
        this._lastOvershoot = -1;
    }

    _handleDragOvershoot(dragEvent) {
        const [gridX, gridY] = this.get_transformed_position();
        const [gridWidth, gridHeight] = this.get_transformed_size();

        const vertical = this._orientation === Clutter.Orientation.VERTICAL;
        const gridStart = vertical ? gridY : gridX;
        const gridEnd = vertical
            ? gridY + gridHeight - OVERSHOOT_THRESHOLD
            : gridX + gridWidth - OVERSHOOT_THRESHOLD;

        // Already animating
        if (this._adjustment.get_transition('value') !== null)
            return;

        // Within the grid boundaries
        const dragPosition = vertical ? dragEvent.y : dragEvent.x;
        if (dragPosition > gridStart && dragPosition < gridEnd) {
            // Check whether we moved out the area of the last switch
            if (Math.abs(this._lastOvershoot - dragPosition) > OVERSHOOT_THRESHOLD)
                this._resetOvershoot();

            return;
        }

        // Still in the area of the previous page switch
        if (this._lastOvershoot >= 0)
            return;

        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
        if (dragPosition <= gridStart)
            this.goToPage(this._grid.currentPage + (rtl ? 1 : -1));
        else if (dragPosition >= gridEnd)
            this.goToPage(this._grid.currentPage + (rtl ? -1 : 1));
        else
            return; // don't go beyond first/last page

        this._lastOvershoot = dragPosition;

        if (this._lastOvershootTimeoutId > 0)
            GLib.source_remove(this._lastOvershootTimeoutId);

        this._lastOvershootTimeoutId =
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, OVERSHOOT_TIMEOUT, () => {
                this._resetOvershoot();
                this._handleDragOvershoot(dragEvent);
                return GLib.SOURCE_REMOVE;
            });
        GLib.Source.set_name_by_id(this._lastOvershootTimeoutId,
            '[gnome-shell] this._lastOvershootTimeoutId');
    }

    _onDragBegin() {
        this._dragMonitor = {
            dragMotion: this._onDragMotion.bind(this),
        };
        DND.addDragMonitor(this._dragMonitor);
        this._slideSidePages(SidePages.PREVIOUS | SidePages.NEXT | SidePages.DND);
        this._dragFocus = null;
        this._swipeTracker.enabled = false;
    }

    _onDragMotion(dragEvent) {
        if (!(dragEvent.source instanceof AppViewItem))
            return DND.DragMotionResult.CONTINUE;

        const appIcon = dragEvent.source;

        this._dropPage = this._pageForCoords(dragEvent.x, dragEvent.y);
        if (this._dropPage &&
            this._dropPage === SidePages.PREVIOUS &&
            this._grid.currentPage === 0) {
            delete this._dropPage;
            return DND.DragMotionResult.NO_DROP;
        }

        // Handle the drag overshoot. When dragging to above the
        // icon grid, move to the page above; when dragging below,
        // move to the page below.
        if (appIcon instanceof AppViewItem)
            this._handleDragOvershoot(dragEvent);

        this._maybeMoveItem(dragEvent);

        return DND.DragMotionResult.CONTINUE;
    }

    _onDragEnd() {
        if (this._dragMonitor) {
            DND.removeDragMonitor(this._dragMonitor);
            this._dragMonitor = null;
        }

        this._resetOvershoot();
        this._slideSidePages(SidePages.NONE);
        delete this._dropPage;
        this._swipeTracker.enabled = true;
    }

    _onDragCancelled() {
        // At this point, the positions aren't stored yet, thus _redisplay()
        // will move all items to their original positions
        this._redisplay();
        this._slideSidePages(SidePages.NONE);
        this._swipeTracker.enabled = true;
    }

    _canAccept(source) {
        return source instanceof AppViewItem;
    }

    handleDragOver(source) {
        if (!this._canAccept(source))
            return DND.DragMotionResult.NO_DROP;

        return DND.DragMotionResult.MOVE_DROP;
    }

    acceptDrop(source) {
        if (!this._canAccept(source))
            return false;

        if (this._dropPage) {
            const increment = this._dropPage === SidePages.NEXT ? 1 : -1;
            const { currentPage, nPages } = this._grid;
            const page = Math.min(currentPage + increment, nPages);
            const position = page < nPages ? -1 : 0;

            this._moveItem(source, page, position);
            this.goToPage(page);
        } else if (this._delayedMoveData) {
            // Dropped before the icon was moved
            const { page, position } = this._delayedMoveData;

            this._moveItem(source, page, position);
            this._removeDelayedMove();
        }

        return true;
    }

    _findBestPageToAppend(startPage = 1) {
        for (let i = startPage; i < this._grid.nPages; i++) {
            const pageItems =
                this._grid.getItemsAtPage(i).filter(c => c.visible);

            if (pageItems.length < this._grid.itemsPerPage)
                return i;
        }

        return -1;
    }

    _getLinearPosition(page, position) {
        let itemIndex = 0;

        if (this._grid.nPages > 0) {
            const realPage = page === -1 ? this._grid.nPages - 1 : page;

            itemIndex = position === -1
                ? this._grid.getItemsAtPage(realPage).filter(c => c.visible).length - 1
                : position;

            for (let i = 0; i < realPage; i++) {
                const pageItems = this._grid.getItemsAtPage(i).filter(c => c.visible);
                itemIndex += pageItems.length;
            }
        }

        return itemIndex;
    }

    _addItem(item, page, position) {
        // Append icons to the first page with empty slot, starting from
        // the second page
        if (this._grid.nPages > 1 && page === -1 && position === -1)
            page = this._findBestPageToAppend();

        const itemIndex = this._getLinearPosition(page, position);

        this._orderedItems.splice(itemIndex, 0, item);
        this._items.set(item.id, item);
        this._grid.addItem(item, page, position);
    }

    _removeItem(item) {
        const iconIndex = this._orderedItems.indexOf(item);

        this._orderedItems.splice(iconIndex, 1);
        this._items.delete(item.id);
        this._grid.removeItem(item);
    }

    _getItemPosition(item) {
        const { itemsPerPage } = this._grid;

        let iconIndex = this._orderedItems.indexOf(item);
        if (iconIndex === -1)
            iconIndex = this._orderedItems.length - 1;

        const page = Math.floor(iconIndex / itemsPerPage);
        const position = iconIndex % itemsPerPage;

        return [page, position];
    }

    _redisplay() {
        let oldApps = this._orderedItems.slice();
        let oldAppIds = oldApps.map(icon => icon.id);

        let newApps = this._loadApps().sort(this._compareItems.bind(this));
        let newAppIds = newApps.map(icon => icon.id);

        let addedApps = newApps.filter(icon => !oldAppIds.includes(icon.id));
        let removedApps = oldApps.filter(icon => !newAppIds.includes(icon.id));

        // Remove old app icons
        removedApps.forEach(icon => {
            this._removeItem(icon);
            icon.destroy();
        });

        // Add new app icons, or move existing ones
        newApps.forEach(icon => {
            const [page, position] = this._getItemPosition(icon);
            if (addedApps.includes(icon))
                this._addItem(icon, page, position);
            else if (page !== -1 && position !== -1)
                this._moveItem(icon, page, position);
        });

        this.emit('view-loaded');
    }

    getAllItems() {
        return this._orderedItems;
    }

    _compareItems(a, b) {
        return a.name.localeCompare(b.name);
    }

    _selectAppInternal(id) {
        if (this._items.has(id))
            this._items.get(id).navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
        else
            log(`No such application ${id}`);
    }

    selectApp(id) {
        if (this._items.has(id)) {
            let item = this._items.get(id);

            if (item.mapped) {
                this._selectAppInternal(id);
            } else {
                // Need to wait until the view is mapped
                let signalId = item.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);
            });
        }
    }

    _getDropTarget(x, y, source) {
        const { currentPage } = this._grid;

        let [item, dragLocation] = this._grid.getDropTarget(x, y);

        const [sourcePage, sourcePosition] = this._grid.getItemPosition(source);
        const targetPage = currentPage;
        let targetPosition = item
            ? this._grid.getItemPosition(item)[1] : -1;

        // In case we're hovering over the edge of an item but the
        // reflow will happen in the opposite direction (the drag
        // can't "naturally push the item away"), we instead set the
        // drop target to the adjacent item that can be pushed away
        // in the reflow-direction.
        //
        // We must avoid doing that if we're hovering over the first
        // or last column though, in that case there is no adjacent
        // icon we could push away.
        if (dragLocation === IconGrid.DragLocation.START_EDGE &&
            targetPosition > sourcePosition &&
            targetPage === sourcePage) {
            const nColumns = this._grid.layout_manager.columns_per_page;
            const targetColumn = targetPosition % nColumns;

            if (targetColumn > 0) {
                targetPosition -= 1;
                dragLocation = IconGrid.DragLocation.END_EDGE;
            }
        } else if (dragLocation === IconGrid.DragLocation.END_EDGE &&
            (targetPosition < sourcePosition ||
             targetPage !== sourcePage)) {
            const nColumns = this._grid.layout_manager.columns_per_page;
            const targetColumn = targetPosition % nColumns;

            if (targetColumn < nColumns - 1) {
                targetPosition += 1;
                dragLocation = IconGrid.DragLocation.START_EDGE;
            }
        }

        // Append to the page if dragging over empty area
        if (dragLocation === IconGrid.DragLocation.EMPTY_SPACE) {
            const pageItems =
                this._grid.getItemsAtPage(currentPage).filter(c => c.visible);

            targetPosition = pageItems.length;
        }

        return [targetPage, targetPosition, dragLocation];
    }

    _moveItem(item, newPage, newPosition) {
        const [page, position] = this._grid.getItemPosition(item);
        if (page === newPage && position === newPosition)
            return;

        // Update the _orderedItems array
        let index = this._orderedItems.indexOf(item);
        this._orderedItems.splice(index, 1);

        index = this._getLinearPosition(newPage, newPosition);
        this._orderedItems.splice(index, 0, item);

        this._grid.moveItem(item, newPage, newPosition);
    }

    vfunc_allocate(box) {
        const width = box.get_width();
        const height = box.get_height();

        this.adaptToSize(width, height);

        super.vfunc_allocate(box);
    }

    vfunc_map() {
        this._swipeTracker.enabled = true;
        this._connectDnD();
        super.vfunc_map();
    }

    vfunc_unmap() {
        this._swipeTracker.enabled = false;
        this._disconnectDnD();
        super.vfunc_unmap();
    }

    animateSwitch(animationDirection) {
        this.remove_all_transitions();
        this._grid.remove_all_transitions();

        let params = {
            duration: VIEWS_SWITCH_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        };
        if (animationDirection == IconGrid.AnimationDirection.IN) {
            this.show();
            params.opacity = 255;
            params.delay = VIEWS_SWITCH_ANIMATION_DELAY;
        } else {
            params.opacity = 0;
            params.delay = 0;
            params.onComplete = () => this.hide();
        }

        this._grid.ease(params);
    }

    _syncPageHints(pageNumber, animate = true) {
        const showingNextPage = this._pagesShown & SidePages.NEXT;
        const showingPrevPage = this._pagesShown & SidePages.PREVIOUS;
        const dnd = this._pagesShown & SidePages.DND;
        const duration = animate ? PAGE_INDICATOR_FADE_TIME : 0;

        if (showingPrevPage) {
            const opacity = pageNumber === 0 ? 0 : 255;
            this._prevPageIndicator.visible = true;
            this._prevPageIndicator.ease({
                opacity,
                duration,
            });

            if (!dnd) {
                this._prevPageArrow.visible = true;
                this._prevPageArrow.ease({
                    opacity,
                    duration,
                });
            }
        }

        if (showingNextPage) {
            const opacity = pageNumber === this._grid.nPages - 1 ? 0 : 255;
            this._nextPageIndicator.visible = true;
            this._nextPageIndicator.ease({
                opacity,
                duration,
            });

            if (!dnd) {
                this._nextPageArrow.visible = true;
                this._nextPageArrow.ease({
                    opacity,
                    duration,
                });
            }
        }
    }

    goToPage(pageNumber, animate = true) {
        pageNumber = Math.clamp(pageNumber, 0, Math.max(this._grid.nPages - 1, 0));

        if (this._grid.currentPage === pageNumber)
            return;

        this._syncPageHints(pageNumber, animate);
        this._grid.goToPage(pageNumber, animate);
    }

    adaptToSize(width, height) {
        let box = new Clutter.ActorBox({
            x2: width,
            y2: height,
        });
        box = this.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);

        const availWidth = box.get_width();
        const availHeight = box.get_height();

        const gridRatio = this._grid.layout_manager.columnsPerPage /
            this._grid.layout_manager.rowsPerPage;
        const spaceRatio = availWidth / availHeight;
        let pageWidth, pageHeight;

        if (spaceRatio > gridRatio * 1.1) {
            // Enough room for some preview
            pageHeight = availHeight;
            pageWidth = Math.ceil(availHeight * gridRatio);

            if (spaceRatio > gridRatio * 1.5) {
                // Ultra-wide layout, give some extra space for
                // the page area, but up to an extent.
                const extraPageSpace = Math.min(
                    Math.floor((availWidth - pageWidth) / 2), MAX_PAGE_PADDING);
                pageWidth += extraPageSpace;
                this._grid.layout_manager.pagePadding.left =
                    Math.floor(extraPageSpace / 2);
                this._grid.layout_manager.pagePadding.right =
                    Math.ceil(extraPageSpace / 2);
            }
        } else {
            // Not enough room, needs to shrink horizontally
            pageWidth = Math.ceil(availWidth * 0.8);
            pageHeight = availHeight;
            this._grid.layout_manager.pagePadding.left =
                Math.floor(availWidth * 0.02);
            this._grid.layout_manager.pagePadding.right =
                Math.ceil(availWidth * 0.02);
        }

        this._grid.adaptToSize(pageWidth, pageHeight);

        const leftPadding = Math.floor(
            (availWidth - this._grid.layout_manager.pageWidth) / 2);
        const rightPadding = Math.ceil(
            (availWidth - this._grid.layout_manager.pageWidth) / 2);
        const topPadding = Math.floor(
            (availHeight - this._grid.layout_manager.pageHeight) / 2);
        const bottomPadding = Math.ceil(
            (availHeight - this._grid.layout_manager.pageHeight) / 2);

        this._scrollView.content_padding = new Clutter.Margin({
            left: leftPadding,
            right: rightPadding,
            top: topPadding,
            bottom: bottomPadding,
        });

        this._availWidth = availWidth;
        this._availHeight = availHeight;

        this._pageIndicatorOffset = leftPadding;
        this._pageArrowOffset = Math.max(
            leftPadding - PAGE_PREVIEW_MAX_ARROW_OFFSET, 0);
    }

    _getIndicatorOffset(page, progress, baseOffset) {
        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;
        const translationX =
            (1 - progress) * PAGE_PREVIEW_ANIMATION_START_OFFSET;

        page = rtl ? -page : page;

        return (translationX - baseOffset) * page;
    }

    _syncClip() {
        const nextPageAdjustment = this._previewedPages.get(1);
        const prevPageAdjustment = this._previewedPages.get(-1);
        this._grid.clip_to_view =
            (!prevPageAdjustment || prevPageAdjustment.value === 0) &&
            (!nextPageAdjustment || nextPageAdjustment.value === 0);
    }

    _setupPagePreview(page, state) {
        if (this._previewedPages.has(page))
            return this._previewedPages.get(page);

        const adjustment = new St.Adjustment({
            actor: this,
            lower: 0,
            upper: 1,
        });

        const indicator = page > 0
            ? this._nextPageIndicator : this._prevPageIndicator;

        adjustment.connectObject('notify::value', () => {
            const nextPage = this._grid.currentPage + page;
            const hasFollowingPage = nextPage >= 0 &&
                nextPage < this._grid.nPages;

            if (hasFollowingPage) {
                const items = this._grid.getItemsAtPage(nextPage);
                items.forEach(item => {
                    item.translation_x =
                        this._getIndicatorOffset(page, adjustment.value, 0);
                });

                if (!(state & SidePages.DND)) {
                    const pageArrow = page > 0
                        ? this._nextPageArrow
                        : this._prevPageArrow;
                    pageArrow.set({
                        visible: true,
                        opacity: adjustment.value * 255,
                        translation_x: this._getIndicatorOffset(
                            page, adjustment.value,
                            this._pageArrowOffset),
                    });
                }
            }
            if (hasFollowingPage ||
                (page > 0 &&
                 this._grid.layout_manager.allow_incomplete_pages &&
                 (state & SidePages.DND) !== 0)) {
                indicator.set({
                    visible: true,
                    opacity: adjustment.value * 255,
                    translation_x: this._getIndicatorOffset(
                        page, adjustment.value,
                        this._pageIndicatorOffset - indicator.width),
                });
            }
            this._syncClip();
        }, this);

        this._previewedPages.set(page, adjustment);

        return adjustment;
    }

    _teardownPagePreview(page) {
        const adjustment = this._previewedPages.get(page);
        if (!adjustment)
            return;

        adjustment.value = 1;
        adjustment.disconnectObject(this);
        this._previewedPages.delete(page);
    }

    _slideSidePages(state) {
        if (this._pagesShown === state)
            return;
        this._pagesShown = state;
        const showingNextPage = state & SidePages.NEXT;
        const showingPrevPage = state & SidePages.PREVIOUS;
        const dnd = state & SidePages.DND;
        let adjustment;

        if (dnd) {
            this._nextPageIndicator.add_style_class_name('dnd');
            this._prevPageIndicator.add_style_class_name('dnd');
        } else {
            this._nextPageIndicator.remove_style_class_name('dnd');
            this._prevPageIndicator.remove_style_class_name('dnd');
        }

        adjustment = this._previewedPages.get(1);
        if (showingNextPage) {
            adjustment = this._setupPagePreview(1, state);

            adjustment.ease(1, {
                duration: PAGE_PREVIEW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
            this._updateFadeForNavigation();
        } else if (adjustment) {
            adjustment.ease(0, {
                duration: PAGE_PREVIEW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._teardownPagePreview(1);
                    this._syncClip();
                    this._nextPageArrow.visible = false;
                    this._nextPageIndicator.visible = false;
                    this._updateFadeForNavigation();
                },
            });
        }

        adjustment = this._previewedPages.get(-1);
        if (showingPrevPage) {
            adjustment = this._setupPagePreview(-1, state);

            adjustment.ease(1, {
                duration: PAGE_PREVIEW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
            this._updateFadeForNavigation();
        } else if (adjustment) {
            adjustment.ease(0, {
                duration: PAGE_PREVIEW_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this._teardownPagePreview(-1);
                    this._syncClip();
                    this._prevPageArrow.visible = false;
                    this._prevPageIndicator.visible = false;
                    this._updateFadeForNavigation();
                },
            });
        }
    }

    updateDragFocus(dragFocus) {
        this._dragFocus = dragFocus;
    }
});

var PageManager = GObject.registerClass({
    Signals: { 'layout-changed': {} },
}, class PageManager extends GObject.Object {
    _init() {
        super._init();

        this._updatingPages = false;
        this._loadPages();

        global.settings.connect('changed::app-picker-layout',
            this._loadPages.bind(this));
    }

    _loadPages() {
        const layout = global.settings.get_value('app-picker-layout');
        this._pages = layout.recursiveUnpack();
        if (!this._updatingPages)
            this.emit('layout-changed');
    }

    getAppPosition(appId) {
        let position = -1;
        let page = -1;

        for (let pageIndex = 0; pageIndex < this._pages.length; pageIndex++) {
            const pageData = this._pages[pageIndex];

            if (appId in pageData) {
                page = pageIndex;
                position = pageData[appId].position;
                break;
            }
        }

        return [page, position];
    }

    set pages(p) {
        const packedPages = [];

        // Pack the icon properties as a GVariant
        for (const page of p) {
            const pageData = {};
            for (const [appId, properties] of Object.entries(page))
                pageData[appId] = new GLib.Variant('a{sv}', properties);
            packedPages.push(pageData);
        }

        this._updatingPages = true;

        const variant = new GLib.Variant('aa{sv}', packedPages);
        global.settings.set_value('app-picker-layout', variant);

        this._updatingPages = false;
    }

    get pages() {
        return this._pages;
    }
});

var AppDisplay = GObject.registerClass(
class AppDisplay extends BaseAppView {
    _init() {
        super._init({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });

        this._pageManager = new PageManager();
        this._pageManager.connect('layout-changed', () => this._redisplay());

        this._stack = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });
        this.add_actor(this._stack);
        this._stack.add_child(this._box);

        this._folderIcons = [];

        this._currentDialog = null;
        this._displayingDialog = false;

        this._placeholder = null;

        this._overviewHiddenId = 0;
        this._redisplayWorkId = Main.initializeDeferredWork(this, () => {
            this._redisplay();
            if (this._overviewHiddenId === 0)
                this._overviewHiddenId = Main.overview.connect('hidden', () => this.goToPage(0));
        });

        Shell.AppSystem.get_default().connect('installed-changed', () => {
            Main.queueDeferredWork(this._redisplayWorkId);
        });
        this._folderSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
        this._ensureDefaultFolders();
        this._folderSettings.connect('changed::folder-children', () => {
            Main.queueDeferredWork(this._redisplayWorkId);
        });
    }

    _onDestroy() {
        super._onDestroy();

        if (this._scrollTimeoutId !== 0) {
            GLib.source_remove(this._scrollTimeoutId);
            this._scrollTimeoutId = 0;
        }
    }

    vfunc_map() {
        this._keyPressEventId =
            global.stage.connect('key-press-event',
                this._onKeyPressEvent.bind(this));
        super.vfunc_map();
    }

    vfunc_unmap() {
        if (this._keyPressEventId) {
            global.stage.disconnect(this._keyPressEventId);
            this._keyPressEventId = 0;
        }
        super.vfunc_unmap();
    }

    _redisplay() {
        this._folderIcons.forEach(icon => {
            icon.view._redisplay();
        });

        super._redisplay();
    }

    adaptToSize(width, height) {
        const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1);
        height -= indicatorHeight;

        this._grid.findBestModeForSize(width, height);
        super.adaptToSize(width, height);
    }

    _savePages() {
        const pages = [];

        for (let i = 0; i < this._grid.nPages; i++) {
            const pageItems =
                this._grid.getItemsAtPage(i).filter(c => c.visible);
            const pageData = {};

            pageItems.forEach((item, index) => {
                pageData[item.id] = {
                    position: GLib.Variant.new_int32(index),
                };
            });
            pages.push(pageData);
        }

        this._pageManager.pages = pages;
    }

    _ensureDefaultFolders() {
        if (this._folderSettings.get_strv('folder-children').length > 0)
            return;

        const folders = Object.keys(DEFAULT_FOLDERS);
        this._folderSettings.set_strv('folder-children', folders);

        const { path } = this._folderSettings;
        for (const folder of folders) {
            const { name, categories, apps } = DEFAULT_FOLDERS[folder];
            const child = new Gio.Settings({
                schema_id: 'org.gnome.desktop.app-folders.folder',
                path: `${path}folders/${folder}/`,
            });
            child.set_string('name', name);
            child.set_boolean('translate', true);
            child.set_strv('categories', categories);
            if (apps)
                child.set_strv('apps', apps);
        }
    }

    _ensurePlaceholder(source) {
        if (this._placeholder)
            return;

        const appSys = Shell.AppSystem.get_default();
        const app = appSys.lookup_app(source.id);

        const isDraggable =
            global.settings.is_writable('favorite-apps') ||
            global.settings.is_writable('app-picker-layout');

        this._placeholder = new AppIcon(app, { isDraggable });
        this._placeholder.connect('notify::pressed', icon => {
            if (icon.pressed)
                this.updateDragFocus(icon);
        });
        this._placeholder.scaleAndFade();
        this._redisplay();
    }

    _removePlaceholder() {
        if (this._placeholder) {
            this._placeholder.undoScaleAndFade();
            this._placeholder = null;
            this._redisplay();
        }
    }

    getAppInfos() {
        return this._appInfoList;
    }

    _getItemPosition(item) {
        if (item === this._placeholder) {
            let [page, position] = this._grid.getItemPosition(item);

            if (page === -1)
                page = this._findBestPageToAppend(this._grid.currentPage);

            return [page, position];
        }

        return this._pageManager.getAppPosition(item.id);
    }

    _compareItems(a, b) {
        const [aPage, aPosition] = this._getItemPosition(a);
        const [bPage, bPosition] = this._getItemPosition(b);

        if (aPage === -1 && bPage === -1)
            return a.name.localeCompare(b.name);
        else if (aPage === -1)
            return 1;
        else if (bPage === -1)
            return -1;

        if (aPage !== bPage)
            return aPage - bPage;

        return aPosition - bPosition;
    }

    _loadApps() {
        let appIcons = [];
        this._appInfoList = Shell.AppSystem.get_default().get_installed().filter(appInfo => {
            try {
                appInfo.get_id(); // catch invalid file encodings
            } catch (e) {
                return false;
            }
            return !this._appFavorites.isFavorite(appInfo.get_id()) &&
                this._parentalControlsManager.shouldShowApp(appInfo);
        });

        let apps = this._appInfoList.map(app => app.get_id());

        let appSys = Shell.AppSystem.get_default();

        const appsInsideFolders = new Set();
        this._folderIcons = [];

        let folders = this._folderSettings.get_strv('folder-children');
        folders.forEach(id => {
            let path = `${this._folderSettings.path}folders/${id}/`;
            let icon = this._items.get(id);
            if (!icon) {
                icon = new FolderIcon(id, path, this);
                icon.connect('apps-changed', () => {
                    this._redisplay();
                    this._savePages();
                });
                icon.connect('notify::pressed', () => {
                    if (icon.pressed)
                        this.updateDragFocus(icon);
                });
            }

            // Don't try to display empty folders
            if (!icon.visible) {
                icon.destroy();
                return;
            }

            appIcons.push(icon);
            this._folderIcons.push(icon);

            icon.getAppIds().forEach(appId => appsInsideFolders.add(appId));
        });

        // 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.
        const isDraggable =
            global.settings.is_writable('favorite-apps') ||
            global.settings.is_writable('app-picker-layout');

        apps.forEach(appId => {
            if (appsInsideFolders.has(appId))
                return;

            let icon = this._items.get(appId);
            if (!icon) {
                let app = appSys.lookup_app(appId);

                icon = new AppIcon(app, { isDraggable });
                icon.connect('notify::pressed', () => {
                    if (icon.pressed)
                        this.updateDragFocus(icon);
                });
            }

            appIcons.push(icon);
        });

        // At last, if there's a placeholder available, add it
        if (this._placeholder)
            appIcons.push(this._placeholder);

        return appIcons;
    }

    animateSwitch(animationDirection) {
        super.animateSwitch(animationDirection);

        if (this._currentDialog && this._displayingDialog &&
            animationDirection == IconGrid.AnimationDirection.OUT) {
            this._currentDialog.ease({
                opacity: 0,
                duration: VIEWS_SWITCH_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => (this.opacity = 255),
            });
        }
    }

    goToPage(pageNumber, animate = true) {
        pageNumber = Math.clamp(pageNumber, 0, Math.max(this._grid.nPages - 1, 0));

        if (this._grid.currentPage === pageNumber &&
            this._displayingDialog &&
            this._currentDialog)
            return;
        if (this._displayingDialog && this._currentDialog)
            this._currentDialog.popdown();

        super.goToPage(pageNumber, animate);
    }

    _onScroll(actor, event) {
        if (this._displayingDialog || !this._scrollView.reactive)
            return Clutter.EVENT_STOP;

        return super._onScroll(actor, event);
    }

    _onKeyPressEvent(actor, event) {
        if (this._displayingDialog)
            return Clutter.EVENT_STOP;

        if (event.get_key_symbol() === Clutter.KEY_Page_Up) {
            this.goToPage(this._grid.currentPage - 1);
            return Clutter.EVENT_STOP;
        } else if (event.get_key_symbol() === Clutter.KEY_Page_Down) {
            this.goToPage(this._grid.currentPage + 1);
            return Clutter.EVENT_STOP;
        } else if (event.get_key_symbol() === Clutter.KEY_Home) {
            this.goToPage(0);
            return Clutter.EVENT_STOP;
        } else if (event.get_key_symbol() === Clutter.KEY_End) {
            this.goToPage(this._grid.nPages - 1);
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    addFolderDialog(dialog) {
        Main.layoutManager.overviewGroup.add_child(dialog);
        dialog.connect('open-state-changed', (o, isOpen) => {
            this._currentDialog?.disconnectObject(this);

            this._currentDialog = null;

            if (isOpen) {
                this._currentDialog = dialog;
                this._currentDialog.connectObject('destroy',
                    () => (this._currentDialog = null), this);
            }
            this._displayingDialog = isOpen;
        });
    }

    _maybeMoveItem(dragEvent) {
        const clonedEvent = {
            ...dragEvent,
            source: this._placeholder ? this._placeholder : dragEvent.source,
        };

        super._maybeMoveItem(clonedEvent);
    }

    _onDragBegin(overview, source) {
        super._onDragBegin(overview, source);

        // When dragging from a folder dialog, the dragged app icon doesn't
        // exist in AppDisplay. We work around that by adding a placeholder
        // icon that is either destroyed on cancel, or becomes the effective
        // new icon when dropped.
        if (_getViewFromIcon(source) instanceof FolderView ||
            this._appFavorites.isFavorite(source.id))
            this._ensurePlaceholder(source);
    }

    _onDragMotion(dragEvent) {
        if (this._currentDialog)
            return DND.DragMotionResult.CONTINUE;

        return super._onDragMotion(dragEvent);
    }

    _onDragEnd() {
        super._onDragEnd();
        this._removePlaceholder();
        this._savePages();
    }

    _onDragCancelled(overview, source) {
        const view = _getViewFromIcon(source);

        if (view instanceof FolderView)
            return;

        super._onDragCancelled(overview, source);
    }

    acceptDrop(source) {
        if (!super.acceptDrop(source))
            return false;

        this._savePages();

        let view = _getViewFromIcon(source);
        if (view instanceof FolderView)
            view.removeApp(source.app);

        if (this._currentDialog)
            this._currentDialog.popdown();

        if (this._appFavorites.isFavorite(source.id))
            this._appFavorites.removeFavorite(source.id);

        return true;
    }

    createFolder(apps) {
        let newFolderId = GLib.uuid_string_random();

        let folders = this._folderSettings.get_strv('folder-children');
        folders.push(newFolderId);
        this._folderSettings.set_strv('folder-children', folders);

        // Create the new folder
        let newFolderPath = this._folderSettings.path.concat('folders/', newFolderId, '/');
        let newFolderSettings;
        try {
            newFolderSettings = new Gio.Settings({
                schema_id: 'org.gnome.desktop.app-folders.folder',
                path: newFolderPath,
            });
        } catch (e) {
            log('Error creating new folder');
            return false;
        }

        // The hovered AppIcon always passes its own id as the first
        // one, and this is where we want the folder to be created
        let [folderPage, folderPosition] =
            this._grid.getItemPosition(this._items.get(apps[0]));

        // Adjust the final position
        folderPosition -= apps.reduce((counter, appId) => {
            const [page, position] =
                this._grid.getItemPosition(this._items.get(appId));
            if (page === folderPage && position < folderPosition)
                counter++;
            return counter;
        }, 0);

        let appItems = apps.map(id => this._items.get(id).app);
        let folderName = _findBestFolderName(appItems);
        if (!folderName)
            folderName = _("Unnamed Folder");

        newFolderSettings.delay();
        newFolderSettings.set_string('name', folderName);
        newFolderSettings.set_strv('apps', apps);
        newFolderSettings.apply();

        this._redisplay();

        // Move the folder to where the icon target icon was
        const folderItem = this._items.get(newFolderId);
        this._moveItem(folderItem, folderPage, folderPosition);
        this._savePages();

        return true;
    }
});

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();

        this._parentalControlsManager = ParentalControlsManager.getDefault();
    }

    getResultMetas(apps, callback) {
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        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 => app.create_icon_texture(size),
                });
            } else {
                let name = this._systemActions.getName(id);
                let iconName = this._systemActions.getIconName(id);

                const createIcon = size => new St.Icon({
                    icon_name: iconName,
                    width: size * scaleFactor,
                    height: size * scaleFactor,
                    style_class: 'system-action-icon',
                });

                metas.push({ id, name, createIcon });
            }
        }

        callback(metas);
    }

    filterResults(results, maxNumber) {
        return results.slice(0, maxNumber);
    }

    getInitialResultSet(terms, callback, _cancellable) {
        // Defer until the parental controls manager is initialised, so the
        // results can be filtered correctly.
        if (!this._parentalControlsManager.initialized) {
            let initializedId = this._parentalControlsManager.connect('app-filter-changed', () => {
                if (this._parentalControlsManager.initialized) {
                    this._parentalControlsManager.disconnect(initializedId);
                    this.getInitialResultSet(terms, callback, _cancellable);
                }
            });
            return;
        }

        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 && this._parentalControlsManager.shouldShowApp(app.app_info);
            });
            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']), {
                expandTitleOnHover: false,
            });
        } else {
            return new SystemActionIcon(this, resultMeta);
        }
    }
};

var AppViewItem = GObject.registerClass(
class AppViewItem extends St.Button {
    _init(params = {}, isDraggable = true, expandTitleOnHover = true) {
        super._init({
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
            reactive: true,
            button_mask: St.ButtonMask.ONE | St.ButtonMask.TWO,
            can_focus: true,
            ...params,
        });

        this._delegate = this;

        if (isDraggable) {
            this._draggable = DND.makeDraggable(this, { timeoutThreshold: 200 });

            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._otherIconIsHovering = false;
        this._expandTitleOnHover = expandTitleOnHover;

        if (expandTitleOnHover)
            this.connect('notify::hover', this._onHover.bind(this));
        this.connect('destroy', this._onDestroy.bind(this));
    }

    _onDestroy() {
        if (this._dragMonitor) {
            DND.removeDragMonitor(this._dragMonitor);
            this._dragMonitor = null;
        }

        if (this._draggable) {
            if (this._dragging)
                Main.overview.endItemDrag(this);
            this._draggable = null;
        }
    }

    _updateMultiline() {
        if (!this._expandTitleOnHover || !this.icon.label)
            return;

        const { label } = this.icon;
        const { clutterText } = label;
        const layout = clutterText.get_layout();
        if (!layout.is_wrapped() && !layout.is_ellipsized())
            return;

        label.remove_transition('allocation');

        const id = label.connect('notify::allocation', () => {
            label.restore_easing_state();
            label.disconnect(id);
        });

        const expand = this._forcedHighlight || this.hover || this.has_key_focus();
        label.save_easing_state();
        label.set_easing_duration(expand
            ? APP_ICON_TITLE_EXPAND_TIME
            : APP_ICON_TITLE_COLLAPSE_TIME);
        clutterText.set({
            line_wrap: expand,
            line_wrap_mode: expand ? Pango.WrapMode.WORD_CHAR : Pango.WrapMode.NONE,
            ellipsize: expand ? Pango.EllipsizeMode.NONE : Pango.EllipsizeMode.END,
        });
    }

    _onHover() {
        this._updateMultiline();
    }

    _onDragBegin() {
        this._dragging = true;
        this.scaleAndFade();
        Main.overview.beginItemDrag(this);
    }

    _onDragCancelled() {
        this._dragging = false;
        Main.overview.cancelledItemDrag(this);
    }

    _onDragEnd() {
        this._dragging = false;
        this.undoScaleAndFade();
        Main.overview.endItemDrag(this);
    }

    scaleIn() {
        this.scale_x = 0;
        this.scale_y = 0;

        this.ease({
            scale_x: 1,
            scale_y: 1,
            duration: APP_ICON_SCALE_IN_TIME,
            delay: APP_ICON_SCALE_IN_DELAY,
            mode: Clutter.AnimationMode.EASE_OUT_QUINT,
        });
    }

    scaleAndFade() {
        this.reactive = false;
        this.ease({
            scale_x: 0.5,
            scale_y: 0.5,
            opacity: 0,
        });
    }

    undoScaleAndFade() {
        this.reactive = true;
        this.ease({
            scale_x: 1.0,
            scale_y: 1.0,
            opacity: 255,
        });
    }

    _canAccept(source) {
        return source !== this;
    }

    _setHoveringByDnd(hovering) {
        if (this._otherIconIsHovering === hovering)
            return;

        this._otherIconIsHovering = hovering;

        if (hovering) {
            this._dragMonitor = {
                dragMotion: this._onDragMotion.bind(this),
            };
            DND.addDragMonitor(this._dragMonitor);
        } else {
            DND.removeDragMonitor(this._dragMonitor);
        }
    }

    _onDragMotion(dragEvent) {
        if (!this.contains(dragEvent.targetActor))
            this._setHoveringByDnd(false);

        return DND.DragMotionResult.CONTINUE;
    }

    _withinLeeways(x) {
        return x < IconGrid.LEFT_DIVIDER_LEEWAY ||
            x > this.width - IconGrid.RIGHT_DIVIDER_LEEWAY;
    }

    vfunc_key_focus_in() {
        this._updateMultiline();
        super.vfunc_key_focus_in();
    }

    vfunc_key_focus_out() {
        this._updateMultiline();
        super.vfunc_key_focus_out();
    }

    handleDragOver(source, _actor, x) {
        if (source === this)
            return DND.DragMotionResult.NO_DROP;

        if (!this._canAccept(source))
            return DND.DragMotionResult.CONTINUE;

        if (this._withinLeeways(x)) {
            this._setHoveringByDnd(false);
            return DND.DragMotionResult.CONTINUE;
        }

        this._setHoveringByDnd(true);

        return DND.DragMotionResult.MOVE_DROP;
    }

    acceptDrop(source, _actor, x) {
        this._setHoveringByDnd(false);

        if (!this._canAccept(source))
            return false;

        if (this._withinLeeways(x))
            return false;

        return true;
    }

    cancelActions() {
        if (this._draggable)
            this._draggable.fakeRelease();
        this.fake_release();
    }

    get id() {
        return this._id;
    }

    get name() {
        return this._name;
    }

    setForcedHighlight(highlighted) {
        this._forcedHighlight = highlighted;
        this.set({
            track_hover: !highlighted,
            hover: highlighted,
        });
    }
});

var FolderGrid = GObject.registerClass(
class FolderGrid extends IconGrid.IconGrid {
    _init() {
        super._init({
            allow_incomplete_pages: false,
            columns_per_page: 3,
            rows_per_page: 3,
            page_halign: Clutter.ActorAlign.CENTER,
            page_valign: Clutter.ActorAlign.CENTER,
        });
    }

    adaptToSize(width, height) {
        this.layout_manager.adaptToSize(width, height);
    }
});

var FolderView = GObject.registerClass(
class FolderView extends BaseAppView {
    _init(folder, id, parentView) {
        super._init({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
            gesture_modes: Shell.ActionMode.POPUP,
        });

        // 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._id = id;
        this._folder = folder;
        this._parentView = parentView;
        this._grid._delegate = this;

        this.add_child(this._box);

        let action = new Clutter.PanAction({ interpolate: true });
        action.connect('pan', this._onPan.bind(this));
        this._scrollView.add_action(action);

        this._deletingFolder = false;
        this._appIds = [];
        this._redisplay();
    }

    _createGrid() {
        return new FolderGrid();
    }

    _getFolderApps() {
        const appIds = [];
        const excludedApps = this._folder.get_strv('excluded-apps');
        const appSys = Shell.AppSystem.get_default();
        const addAppId = appId => {
            if (excludedApps.includes(appId))
                return;

            if (this._appFavorites.isFavorite(appId))
                return;

            const app = appSys.lookup_app(appId);
            if (!app)
                return;

            if (!this._parentalControlsManager.shouldShowApp(app.get_app_info()))
                return;

            if (appIds.indexOf(appId) !== -1)
                return;

            appIds.push(appId);
        };

        const folderApps = this._folder.get_strv('apps');
        folderApps.forEach(addAppId);

        const folderCategories = this._folder.get_strv('categories');
        const appInfos = this._parentView.getAppInfos();
        appInfos.forEach(appInfo => {
            let appCategories = _getCategories(appInfo);
            if (!_listsIntersect(folderCategories, appCategories))
                return;

            addAppId(appInfo.get_id());
        });

        return appIds;
    }

    _getItemPosition(item) {
        const appIndex = this._appIds.indexOf(item.id);

        if (appIndex === -1)
            return [-1, -1];

        const { itemsPerPage } = this._grid;
        return [Math.floor(appIndex / itemsPerPage), appIndex % itemsPerPage];
    }

    _compareItems(a, b) {
        const aPosition = this._appIds.indexOf(a.id);
        const bPosition = this._appIds.indexOf(b.id);

        if (aPosition === -1 && bPosition === -1)
            return a.name.localeCompare(b.name);
        else if (aPosition === -1)
            return 1;
        else if (bPosition === -1)
            return -1;

        return aPosition - bPosition;
    }

    createFolderIcon(size) {
        const layout = new Clutter.GridLayout({
            row_homogeneous: true,
            column_homogeneous: true,
        });
        let icon = new St.Widget({
            layout_manager: layout,
            x_align: Clutter.ActorAlign.CENTER,
            style: `width: ${size}px; height: ${size}px;`,
        });

        let subSize = Math.floor(FOLDER_SUBICON_FRACTION * size);

        let numItems = this._orderedItems.length;
        let rtl = icon.get_text_direction() == Clutter.TextDirection.RTL;
        for (let i = 0; i < 4; i++) {
            const style = `width: ${subSize}px; height: ${subSize}px;`;
            let bin = new St.Bin({ style });
            if (i < numItems)
                bin.child = this._orderedItems[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._scrollView.vscroll.adjustment;
        adjustment.value -= (dy / this._scrollView.height) * adjustment.page_size;
        return false;
    }

    adaptToSize(width, height) {
        const [, indicatorHeight] = this._pageIndicators.get_preferred_height(-1);
        height -= indicatorHeight;

        super.adaptToSize(width, height);
    }

    _loadApps() {
        let apps = [];
        let appSys = Shell.AppSystem.get_default();

        this._appIds.forEach(appId => {
            const app = appSys.lookup_app(appId);

            let icon = this._items.get(appId);
            if (!icon)
                icon = new AppIcon(app);

            apps.push(icon);
        });

        return apps;
    }

    _redisplay() {
        // Keep the app ids list cached
        this._appIds = this._getFolderApps();

        super._redisplay();
    }

    acceptDrop(source) {
        if (!super.acceptDrop(source))
            return false;

        const folderApps = this._orderedItems.map(item => item.id);
        this._folder.set_strv('apps', folderApps);

        return true;
    }

    addApp(app) {
        let folderApps = this._folder.get_strv('apps');
        folderApps.push(app.id);

        this._folder.set_strv('apps', folderApps);

        // Also remove from 'excluded-apps' if the app id is listed
        // there. This is only possible on categories-based folders.
        let excludedApps = this._folder.get_strv('excluded-apps');
        let index = excludedApps.indexOf(app.id);
        if (index >= 0) {
            excludedApps.splice(index, 1);
            this._folder.set_strv('excluded-apps', excludedApps);
        }
    }

    removeApp(app) {
        let folderApps = this._folder.get_strv('apps');
        let index = folderApps.indexOf(app.id);
        if (index >= 0)
            folderApps.splice(index, 1);

        // Remove the folder if this is the last app icon; otherwise,
        // just remove the icon
        if (folderApps.length == 0) {
            this._deletingFolder = true;

            // Resetting all keys deletes the relocatable schema
            let keys = this._folder.settings_schema.list_keys();
            for (const key of keys)
                this._folder.reset(key);

            let settings = new Gio.Settings({ schema_id: 'org.gnome.desktop.app-folders' });
            let folders = settings.get_strv('folder-children');
            folders.splice(folders.indexOf(this._id), 1);
            settings.set_strv('folder-children', folders);

            this._deletingFolder = false;
        } else {
            // If this is a categories-based folder, also add it to
            // the list of excluded apps
            const categories = this._folder.get_strv('categories');
            if (categories.length > 0) {
                const excludedApps = this._folder.get_strv('excluded-apps');
                excludedApps.push(app.id);
                this._folder.set_strv('excluded-apps', excludedApps);
            }

            this._folder.set_strv('apps', folderApps);
        }
    }

    get deletingFolder() {
        return this._deletingFolder;
    }
});

var FolderIcon = GObject.registerClass({
    Signals: {
        'apps-changed': {},
    },
}, class FolderIcon extends AppViewItem {
    _init(id, path, parentView) {
        super._init({
            style_class: 'app-well-app app-folder',
            button_mask: St.ButtonMask.ONE,
            toggle_mode: true,
            can_focus: true,
        }, global.settings.is_writable('app-picker-layout'));
        this._id = id;
        this._name = '';
        this._parentView = parentView;

        this._folder = new Gio.Settings({
            schema_id: 'org.gnome.desktop.app-folders.folder',
            path,
        });

        this.icon = new IconGrid.BaseIcon('', {
            createIcon: this._createIcon.bind(this),
            setSizeManually: true,
        });
        this.set_child(this.icon);
        this.label_actor = this.icon.label;

        this.view = new FolderView(this._folder, id, parentView);

        this._folder.connectObject(
            'changed', this._sync.bind(this), this);
        this._sync();
    }

    _onDestroy() {
        super._onDestroy();

        if (this._dialog)
            this._dialog.destroy();
        else
            this.view.destroy();
    }

    vfunc_clicked() {
        this.open();
    }

    vfunc_unmap() {
        if (this._dialog)
            this._dialog.popdown();

        super.vfunc_unmap();
    }

    open() {
        this._ensureFolderDialog();
        this.view._scrollView.vscroll.adjustment.value = 0;
        this._dialog.popup();
    }

    getAppIds() {
        return this.view.getAllItems().map(item => item.id);
    }

    _setHoveringByDnd(hovering) {
        if (this._otherIconIsHovering == hovering)
            return;

        super._setHoveringByDnd(hovering);

        if (hovering)
            this.add_style_pseudo_class('drop');
        else
            this.remove_style_pseudo_class('drop');
    }

    _onDragMotion(dragEvent) {
        if (!this._canAccept(dragEvent.source))
            this._setHoveringByDnd(false);

        return super._onDragMotion(dragEvent);
    }

    getDragActor() {
        const iconParams = {
            createIcon: this._createIcon.bind(this),
            showLabel: this.icon.label !== null,
            setSizeManually: false,
        };

        const icon = new IconGrid.BaseIcon(this.name, iconParams);
        icon.style_class = this.style_class;

        return icon;
    }

    getDragActorSource() {
        return this;
    }

    _canAccept(source) {
        if (!(source instanceof AppIcon))
            return false;

        let view = _getViewFromIcon(source);
        if (!view || !(view instanceof AppDisplay))
            return false;

        if (this._folder.get_strv('apps').includes(source.id))
            return false;

        return true;
    }

    acceptDrop(source) {
        const accepted = super.acceptDrop(source);

        if (!accepted)
            return false;

        this.view.addApp(source.app);

        return true;
    }

    _updateName() {
        let name = _getFolderName(this._folder);
        if (this.name == name)
            return;

        this._name = name;
        this.icon.label.text = this.name;
    }

    _sync() {
        if (this.view.deletingFolder)
            return;

        this.emit('apps-changed');
        this._updateName();
        this.visible = this.view.getAllItems().length > 0;
        this.icon.update();
    }

    _createIcon(iconSize) {
        return this.view.createFolderIcon(iconSize, this);
    }

    _ensureFolderDialog() {
        if (this._dialog)
            return;
        if (!this._dialog) {
            this._dialog = new AppFolderDialog(this, this._folder,
                this._parentView);
            this._parentView.addFolderDialog(this._dialog);
            this._dialog.connect('open-state-changed', (popup, isOpen) => {
                const duration = FOLDER_DIALOG_ANIMATION_TIME / 2;
                const mode = isOpen
                    ? Clutter.AnimationMode.EASE_OUT_QUAD
                    : Clutter.AnimationMode.EASE_IN_QUAD;

                this.ease({
                    opacity: isOpen ? 0 : 255,
                    duration,
                    mode,
                    delay: isOpen ? 0 : FOLDER_DIALOG_ANIMATION_TIME - duration,
                });

                if (!isOpen)
                    this.checked = false;
            });
        }
    }
});

var AppFolderDialog = GObject.registerClass({
    Signals: {
        'open-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
    },
}, class AppFolderDialog extends St.Bin {
    _init(source, folder, appDisplay) {
        super._init({
            visible: false,
            x_expand: true,
            y_expand: true,
            reactive: true,
        });

        this.add_constraint(new Layout.MonitorConstraint({ primary: true }));

        const clickAction = new Clutter.ClickAction();
        clickAction.connect('clicked', () => {
            const [x, y] = clickAction.get_coords();
            const actor =
                global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);

            if (actor === this)
                this.popdown();
        });
        this.add_action(clickAction);

        this._source = source;
        this._folder = folder;
        this._view = source.view;
        this._appDisplay = appDisplay;
        this._delegate = this;

        this._isOpen = false;

        this._viewBox = new St.BoxLayout({
            style_class: 'app-folder-dialog',
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.FILL,
            y_align: Clutter.ActorAlign.FILL,
            vertical: true,
        });

        this.child = new St.Bin({
            style_class: 'app-folder-dialog-container',
            child: this._viewBox,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this._addFolderNameEntry();
        this._viewBox.add_child(this._view);

        global.focus_manager.add_group(this);

        this._grabHelper = new GrabHelper.GrabHelper(this, {
            actionMode: Shell.ActionMode.POPUP,
        });
        this.connect('destroy', this._onDestroy.bind(this));

        this._dragMonitor = null;
        this._sourceMappedId = 0;
        this._popdownTimeoutId = 0;
        this._needsZoomAndFade = false;

        this._popdownCallbacks = [];
    }

    _addFolderNameEntry() {
        this._entryBox = new St.BoxLayout({
            style_class: 'folder-name-container',
        });
        this._viewBox.add_child(this._entryBox);

        // Empty actor to center the title
        let ghostButton = new Clutter.Actor();
        this._entryBox.add_child(ghostButton);

        let stack = new Shell.Stack({
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
        });
        this._entryBox.add_child(stack);

        // Folder name label
        this._folderNameLabel = new St.Label({
            style_class: 'folder-name-label',
            x_expand: true,
            y_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });

        stack.add_child(this._folderNameLabel);

        // Folder name entry
        this._entry = new St.Entry({
            style_class: 'folder-name-entry',
            opacity: 0,
            reactive: false,
        });
        this._entry.clutter_text.set({
            x_expand: true,
            x_align: Clutter.ActorAlign.CENTER,
        });

        this._entry.clutter_text.connect('activate', () => {
            this._showFolderLabel();
        });

        stack.add_child(this._entry);

        // Edit button
        this._editButton = new St.Button({
            style_class: 'edit-folder-button',
            button_mask: St.ButtonMask.ONE,
            toggle_mode: true,
            reactive: true,
            can_focus: true,
            x_align: Clutter.ActorAlign.END,
            y_align: Clutter.ActorAlign.CENTER,
            child: new St.Icon({
                icon_name: 'document-edit-symbolic',
                icon_size: 16,
            }),
        });

        this._editButton.connect('notify::checked', () => {
            if (this._editButton.checked)
                this._showFolderEntry();
            else
                this._showFolderLabel();
        });

        this._entryBox.add_child(this._editButton);

        ghostButton.add_constraint(new Clutter.BindConstraint({
            source: this._editButton,
            coordinate: Clutter.BindCoordinate.SIZE,
        }));

        this._folder.connect('changed::name', () => this._syncFolderName());
        this._syncFolderName();
    }

    _syncFolderName() {
        let newName = _getFolderName(this._folder);

        this._folderNameLabel.text = newName;
        this._entry.text = newName;
    }

    _switchActor(from, to) {
        to.reactive = true;
        to.ease({
            opacity: 255,
            duration: 300,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        from.ease({
            opacity: 0,
            duration: 300,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                from.reactive = false;
            },
        });
    }

    _showFolderLabel() {
        if (this._editButton.checked)
            this._editButton.checked = false;

        this._maybeUpdateFolderName();
        this._switchActor(this._entry, this._folderNameLabel);
    }

    _showFolderEntry() {
        this._switchActor(this._folderNameLabel, this._entry);

        this._entry.clutter_text.set_selection(0, -1);
        this._entry.clutter_text.grab_key_focus();
    }

    _maybeUpdateFolderName() {
        let folderName = _getFolderName(this._folder);
        let newFolderName = this._entry.text.trim();

        if (newFolderName.length === 0 || newFolderName === folderName)
            return;

        this._folder.set_string('name', newFolderName);
        this._folder.set_boolean('translate', false);
    }

    _zoomAndFadeIn() {
        let [sourceX, sourceY] =
            this._source.get_transformed_position();
        let [dialogX, dialogY] =
            this.child.get_transformed_position();

        this.child.set({
            translation_x: sourceX - dialogX,
            translation_y: sourceY - dialogY,
            scale_x: this._source.width / this.child.width,
            scale_y: this._source.height / this.child.height,
            opacity: 0,
        });

        this.ease({
            background_color: DIALOG_SHADE_NORMAL,
            duration: FOLDER_DIALOG_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
        this.child.ease({
            translation_x: 0,
            translation_y: 0,
            scale_x: 1,
            scale_y: 1,
            opacity: 255,
            duration: FOLDER_DIALOG_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        this._needsZoomAndFade = false;

        if (this._sourceMappedId === 0) {
            this._sourceMappedId = this._source.connect(
                'notify::mapped', this._zoomAndFadeOut.bind(this));
        }
    }

    _zoomAndFadeOut() {
        if (!this._isOpen)
            return;

        if (!this._source.mapped) {
            this.hide();
            return;
        }

        let [sourceX, sourceY] =
            this._source.get_transformed_position();
        let [dialogX, dialogY] =
            this.child.get_transformed_position();

        this.ease({
            background_color: Clutter.Color.from_pixel(0x00000000),
            duration: FOLDER_DIALOG_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        this.child.ease({
            translation_x: sourceX - dialogX,
            translation_y: sourceY - dialogY,
            scale_x: this._source.width / this.child.width,
            scale_y: this._source.height / this.child.height,
            opacity: 0,
            duration: FOLDER_DIALOG_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this.child.set({
                    translation_x: 0,
                    translation_y: 0,
                    scale_x: 1,
                    scale_y: 1,
                    opacity: 255,
                });
                this.hide();

                this._popdownCallbacks.forEach(func => func());
                this._popdownCallbacks = [];
            },
        });

        this._needsZoomAndFade = false;
    }

    _removeDragMonitor() {
        if (!this._dragMonitor)
            return;

        DND.removeDragMonitor(this._dragMonitor);
        this._dragMonitor = null;
    }

    _removePopdownTimeout() {
        if (this._popdownTimeoutId === 0)
            return;

        GLib.source_remove(this._popdownTimeoutId);
        this._popdownTimeoutId = 0;
    }

    _onDestroy() {
        if (this._isOpen) {
            this._isOpen = false;
            this._grabHelper.ungrab({ actor: this });
            this._grabHelper = null;
        }

        if (this._sourceMappedId) {
            this._source.disconnect(this._sourceMappedId);
            this._sourceMappedId = 0;
        }

        this._removePopdownTimeout();
        this._removeDragMonitor();
    }

    vfunc_allocate(box) {
        super.vfunc_allocate(box);

        // We can only start zooming after receiving an allocation
        if (this._needsZoomAndFade)
            this._zoomAndFadeIn();
    }

    vfunc_key_press_event(keyEvent) {
        if (global.stage.get_key_focus() != this)
            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 (keyEvent.keyval) {
        case Clutter.KEY_Down:
            direction = St.DirectionType.TAB_FORWARD;
            break;
        case Clutter.KEY_Right:
            direction = isLtr
                ? St.DirectionType.TAB_FORWARD
                : St.DirectionType.TAB_BACKWARD;
            break;
        case Clutter.KEY_Up:
            direction = St.DirectionType.TAB_BACKWARD;
            break;
        case Clutter.KEY_Left:
            direction = isLtr
                ? St.DirectionType.TAB_BACKWARD
                : St.DirectionType.TAB_FORWARD;
            break;
        default:
            return Clutter.EVENT_PROPAGATE;
        }
        return this.navigate_focus(null, direction, false);
    }

    _setLighterBackground(lighter) {
        const backgroundColor = lighter
            ? DIALOG_SHADE_HIGHLIGHT
            : DIALOG_SHADE_NORMAL;

        this.ease({
            backgroundColor,
            duration: FOLDER_DIALOG_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    _withinDialog(x, y) {
        const childExtents = this.child.get_transformed_extents();
        return childExtents.contains_point(new Graphene.Point({ x, y }));
    }

    _setupDragMonitor() {
        if (this._dragMonitor)
            return;

        this._dragMonitor = {
            dragMotion: dragEvent => {
                const withinDialog =
                    this._withinDialog(dragEvent.x, dragEvent.y);

                this._setLighterBackground(!withinDialog);

                if (withinDialog) {
                    this._removePopdownTimeout();
                    this._removeDragMonitor();
                }
                return DND.DragMotionResult.CONTINUE;
            },
        };
        DND.addDragMonitor(this._dragMonitor);
    }

    _setupPopdownTimeout() {
        if (this._popdownTimeoutId > 0)
            return;

        this._popdownTimeoutId =
            GLib.timeout_add(GLib.PRIORITY_DEFAULT, POPDOWN_DIALOG_TIMEOUT, () => {
                this._popdownTimeoutId = 0;
                this.popdown();
                return GLib.SOURCE_REMOVE;
            });
    }

    handleDragOver(source, actor, x, y) {
        if (this._withinDialog(x, y)) {
            this._setLighterBackground(false);
            this._removePopdownTimeout();
            this._removeDragMonitor();
        } else {
            this._setupPopdownTimeout();
            this._setupDragMonitor();
        }

        return DND.DragMotionResult.MOVE_DROP;
    }

    acceptDrop(source) {
        const appId = source.id;

        this.popdown(() => {
            this._view.removeApp(source);
            this._appDisplay.selectApp(appId);
        });

        return true;
    }

    toggle() {
        if (this._isOpen)
            this.popdown();
        else
            this.popup();
    }

    popup() {
        if (this._isOpen)
            return;

        this._isOpen = this._grabHelper.grab({
            actor: this,
            onUngrab: () => this.popdown(),
        });

        if (!this._isOpen)
            return;

        this.get_parent().set_child_above_sibling(this, null);

        this._needsZoomAndFade = true;
        this.show();

        this.emit('open-state-changed', true);
    }

    popdown(callback) {
        // Either call the callback right away, or wait for the zoom out
        // animation to finish
        if (callback) {
            if (this.visible)
                this._popdownCallbacks.push(callback);
            else
                callback();
        }

        if (!this._isOpen)
            return;

        this._zoomAndFadeOut();
        this._showFolderLabel();

        this._isOpen = false;
        this._grabHelper.ungrab({ actor: this });
        this.emit('open-state-changed', false);
    }
});

var AppIcon = GObject.registerClass({
    Signals: {
        'menu-state-changed': { param_types: [GObject.TYPE_BOOLEAN] },
        'sync-tooltip': {},
    },
}, class AppIcon extends AppViewItem {
    _init(app, iconParams = {}) {
        // Get the isDraggable property without passing it on to the BaseIcon:
        const appIconParams = Params.parse(iconParams, { isDraggable: true }, true);
        const isDraggable = appIconParams['isDraggable'];
        delete iconParams['isDraggable'];
        const expandTitleOnHover = appIconParams['expandTitleOnHover'];
        delete iconParams['expandTitleOnHover'];

        super._init({ style_class: 'app-well-app' }, isDraggable, expandTitleOnHover);

        this.app = app;
        this._id = app.get_id();
        this._name = app.get_name();

        this._iconContainer = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });

        this.set_child(this._iconContainer);

        this._folderPreviewId = 0;

        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._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.add_child(this._dot);

        this.label_actor = this.icon.label;

        this.connect('popup-menu', this._onKeyboardPopupMenu.bind(this));

        this._menu = null;
        this._menuManager = new PopupMenu.PopupMenuManager(this);

        this._menuTimeoutId = 0;
        this.app.connectObject('notify::state',
            () => this._updateRunningStyle(), this);
        this._updateRunningStyle();
    }

    _onDestroy() {
        super._onDestroy();

        if (this._folderPreviewId > 0) {
            GLib.source_remove(this._folderPreviewId);
            this._folderPreviewId = 0;
        }

        this._removeMenuTimeout();
    }

    _onDragBegin() {
        if (this._menu)
            this._menu.close(true);
        this._removeMenuTimeout();
        super._onDragBegin();
    }

    _createIcon(iconSize) {
        return this.app.create_icon_texture(iconSize);
    }

    _removeMenuTimeout() {
        if (this._menuTimeoutId > 0) {
            GLib.source_remove(this._menuTimeoutId);
            this._menuTimeoutId = 0;
        }
    }

    _updateRunningStyle() {
        if (this.app.state != Shell.AppState.STOPPED)
            this._dot.show();
        else
            this._dot.hide();
    }

    _setPopupTimeout() {
        this._removeMenuTimeout();
        this._menuTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, MENU_POPUP_TIMEOUT, () => {
            this._menuTimeoutId = 0;
            this.popupMenu();
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(this._menuTimeoutId, '[gnome-shell] this.popupMenu');
    }

    vfunc_leave_event(crossingEvent) {
        const ret = super.vfunc_leave_event(crossingEvent);

        this.fake_release();
        this._removeMenuTimeout();
        return ret;
    }

    vfunc_button_press_event(buttonEvent) {
        const ret = super.vfunc_button_press_event(buttonEvent);
        if (buttonEvent.button == 1) {
            this._setPopupTimeout();
        } else if (buttonEvent.button == 3) {
            this.popupMenu();
            return Clutter.EVENT_STOP;
        }
        return ret;
    }

    vfunc_touch_event(touchEvent) {
        const ret = super.vfunc_touch_event(touchEvent);
        if (touchEvent.type == Clutter.EventType.TOUCH_BEGIN)
            this._setPopupTimeout();

        return ret;
    }

    vfunc_clicked(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(side = St.Side.LEFT) {
        this.setForcedHighlight(true);
        this._removeMenuTimeout();
        this.fake_release();

        if (!this._menu) {
            this._menu = new AppMenu(this, side, {
                favoritesSection: true,
                showSingleWindows: true,
            });
            this._menu.setApp(this.app);
            this._menu.connect('open-state-changed', (menu, isPoppedUp) => {
                if (!isPoppedUp)
                    this._onMenuPoppedDown();
            });
            Main.overview.connectObject('hiding',
                () => this._menu.close(), this);

            Main.uiGroup.add_actor(this._menu.actor);
            this._menuManager.addMenu(this._menu);
        }

        this.emit('menu-state-changed', true);

        this._menu.open(BoxPointer.PopupAnimation.FULL);
        this._menuManager.ignoreRelease();
        this.emit('sync-tooltip');

        return false;
    }

    _onMenuPoppedDown() {
        this.setForcedHighlight(false);
        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();
    }

    animateLaunchAtPos(x, y) {
        this.icon.animateZoomOutAtPos(x, y);
    }

    shellWorkspaceLaunch(params) {
        let { stack } = new Error();
        log(`shellWorkspaceLaunch is deprecated, use app.open_new_window() instead\n${stack}`);

        params = Params.parse(params, {
            workspace: -1,
            timestamp: 0,
        });

        this.app.open_new_window(params.workspace);
    }

    getDragActor() {
        return this.app.create_icon_texture(Main.overview.dash.iconSize);
    }

    // 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.hover && (!this._menu || !this._menu.isOpen);
    }

    _showFolderPreview() {
        this.icon.label.opacity = 0;
        this.icon.icon.ease({
            scale_x: FOLDER_SUBICON_FRACTION,
            scale_y: FOLDER_SUBICON_FRACTION,
        });
    }

    _hideFolderPreview() {
        this.icon.label.opacity = 255;
        this.icon.icon.ease({
            scale_x: 1.0,
            scale_y: 1.0,
        });
    }

    _canAccept(source) {
        let view = _getViewFromIcon(source);

        return source != this &&
               (source instanceof this.constructor) &&
               (view instanceof AppDisplay);
    }

    _setHoveringByDnd(hovering) {
        if (this._otherIconIsHovering == hovering)
            return;

        super._setHoveringByDnd(hovering);

        if (hovering) {
            if (this._folderPreviewId > 0)
                return;

            this._folderPreviewId =
                GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
                    this.add_style_pseudo_class('drop');
                    this._showFolderPreview();
                    this._folderPreviewId = 0;
                    return GLib.SOURCE_REMOVE;
                });
        } else {
            if (this._folderPreviewId > 0) {
                GLib.source_remove(this._folderPreviewId);
                this._folderPreviewId = 0;
            }
            this._hideFolderPreview();
            this.remove_style_pseudo_class('drop');
        }
    }

    acceptDrop(source, actor, x) {
        const accepted = super.acceptDrop(source, actor, x);
        if (!accepted)
            return false;

        let view = _getViewFromIcon(this);
        let apps = [this.id, source.id];

        return view?.createFolder(apps);
    }

    cancelActions() {
        if (this._menu)
            this._menu.close(true);
        this._removeMenuTimeout();
        super.cancelActions();
    }
});

var SystemActionIcon = GObject.registerClass(
class SystemActionIcon extends Search.GridSearchResult {
    activate() {
        SystemActions.getDefault().activateAction(this.metaInfo['id']);
        Main.overview.hide();
    }
});
(uuay)keyboard.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported KeyboardManager */

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

const EdgeDragAction = imports.ui.edgeDragAction;
const InputSourceManager = imports.ui.status.keyboard;
const IBusManager = imports.misc.ibusManager;
const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const PageIndicators = imports.ui.pageIndicators;
const PopupMenu = imports.ui.popupMenu;

var KEYBOARD_ANIMATION_TIME = 150;
var KEYBOARD_REST_TIME = KEYBOARD_ANIMATION_TIME * 2;
var KEY_LONG_PRESS_TIME = 250;
var PANEL_SWITCH_ANIMATION_TIME = 500;
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', icon: 'keyboard-shift-symbolic' }],
        [{ label: '?123', width: 1.5, level: 2 }],
    ], [
        [],
        [],
        [{ width: 1.5, level: 0, extraClassName: 'shift-key-uppercase', icon: 'keyboard-shift-symbolic' }],
        [{ 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 = [
    [
        [{ width: 1.5, keyval: Clutter.KEY_BackSpace, icon: 'edit-clear-symbolic' }],
        [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key', icon: 'keyboard-enter-symbolic' }],
        [{ width: 3, level: 1, right: true, extraClassName: 'shift-key-lowercase', icon: 'keyboard-shift-symbolic' }],
        [{ action: 'emoji', icon: 'face-smile-symbolic' }, { action: 'languageMenu', extraClassName: 'layout-key', icon: 'keyboard-layout-symbolic' }, { action: 'hide', extraClassName: 'hide-key', icon: 'go-down-symbolic' }],
    ], [
        [{ width: 1.5, keyval: Clutter.KEY_BackSpace, icon: 'edit-clear-symbolic' }],
        [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key', icon: 'keyboard-enter-symbolic' }],
        [{ width: 3, level: 0, right: true, extraClassName: 'shift-key-uppercase', icon: 'keyboard-shift-symbolic' }],
        [{ action: 'emoji', icon: 'face-smile-symbolic' }, { action: 'languageMenu', extraClassName: 'layout-key', icon: 'keyboard-layout-symbolic' }, { action: 'hide', extraClassName: 'hide-key', icon: 'go-down-symbolic' }],
    ], [
        [{ width: 1.5, keyval: Clutter.KEY_BackSpace, icon: 'edit-clear-symbolic' }],
        [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key', icon: 'keyboard-enter-symbolic' }],
        [{ label: '=/<', width: 3, level: 3, right: true }],
        [{ action: 'emoji', icon: 'face-smile-symbolic' }, { action: 'languageMenu', extraClassName: 'layout-key', icon: 'keyboard-layout-symbolic' }, { action: 'hide', extraClassName: 'hide-key', icon: 'go-down-symbolic' }],
    ], [
        [{ width: 1.5, keyval: Clutter.KEY_BackSpace, icon: 'edit-clear-symbolic' }],
        [{ width: 2, keyval: Clutter.KEY_Return, extraClassName: 'enter-key', icon: 'keyboard-enter-symbolic' }],
        [{ label: '?123', width: 3, level: 2, right: true }],
        [{ action: 'emoji', icon: 'face-smile-symbolic' }, { action: 'languageMenu', extraClassName: 'layout-key', icon: 'keyboard-layout-symbolic' }, { action: 'hide', extraClassName: 'hide-key', icon: 'go-down-symbolic' }],
    ],
];

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_get_preferred_width(forHeight) {
        let [min, nat] = super.vfunc_get_preferred_width(forHeight);

        if (forHeight > 0)
            nat = forHeight * this._ratio;

        return [min, nat];
    }

    vfunc_get_preferred_height(forWidth) {
        let [min, nat] = super.vfunc_get_preferred_height(forWidth);

        if (forWidth > 0)
            nat = forWidth / this._ratio;

        return [min, nat];
    }

    vfunc_allocate(box) {
        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);
    }
});

var KeyContainer = GObject.registerClass(
class KeyContainer extends St.Widget {
    _init() {
        const 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() {
        this._currentRow++;
        this._currentCol = 0;

        let row = {
            keys: [],
            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 = GObject.registerClass(
class Suggestions extends St.BoxLayout {
    _init() {
        super._init({
            style_class: 'word-suggestions',
            vertical: false,
            x_align: Clutter.ActorAlign.CENTER,
        });
        this.show();
    }

    add(word, callback) {
        let button = new St.Button({ label: word });
        button.connect('button-press-event', () => {
            callback();
            return Clutter.EVENT_STOP;
        });
        button.connect('touch-event', (actor, event) => {
            if (event.type() !== Clutter.EventType.TOUCH_BEGIN)
                return Clutter.EVENT_PROPAGATE;

            callback();
            return Clutter.EVENT_STOP;
        });
        this.add_child(button);
    }

    clear() {
        this.remove_all_children();
    }

    setVisible(visible) {
        for (const child of this)
            child.visible = visible;
    }
});

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.can_focus = false;
        }

        this.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        item = this.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop');
        item.can_focus = false;

        actor.connectObject('notify::mapped', () => {
            if (!actor.is_mapped())
                this.close(true);
        }, this);
    }

    _onCapturedEvent(actor, event) {
        const targetActor = global.stage.get_event_actor(event);

        if (targetActor === this.actor ||
            this.actor.contains(targetActor))
            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);
        global.stage.connectObject(
            'captured-event', this._onCapturedEvent.bind(this), this);
    }

    close(animate) {
        super.close(animate);
        global.stage.disconnectObject(this);
    }

    destroy() {
        global.stage.disconnectObject(this);
        this.sourceActor.disconnectObject(this);
        super.destroy();
    }
};

var Key = GObject.registerClass({
    Signals: {
        'activated': {},
        'long-press': {},
        'pressed': { param_types: [GObject.TYPE_UINT, GObject.TYPE_STRING] },
        'released': { param_types: [GObject.TYPE_UINT, GObject.TYPE_STRING] },
    },
}, class Key extends St.BoxLayout {
    _init(key, extendedKeys, icon = null) {
        super._init({ style_class: 'key-container' });

        this.key = key || "";
        this.keyButton = this._makeKey(this.key, icon);

        /* Add the key in a container, so keys can be padded without losing
         * logical proportions between those.
         */
        this.add_child(this.keyButton);
        this.connect('destroy', this._onDestroy.bind(this));

        this._extendedKeys = extendedKeys;
        this._extendedKeyboard = null;
        this._pressTimeoutId = 0;
        this._capturedPress = false;
    }

    _onDestroy() {
        if (this._boxPointer) {
            this._boxPointer.destroy();
            this._boxPointer = null;
        }

        this.cancel();
    }

    _ensureExtendedKeysPopup() {
        if (this._extendedKeys.length === 0)
            return;

        if (this._boxPointer)
            return;

        this._boxPointer = new BoxPointer.BoxPointer(St.Side.BOTTOM);
        this._boxPointer.hide();
        Main.layoutManager.addTopChrome(this._boxPointer);
        this._boxPointer.setPosition(this.keyButton, 0.5);

        // Adds style to existing keyboard style to avoid repetition
        this._boxPointer.add_style_class_name('keyboard-subkeys');
        this._getExtendedKeys();
        this.keyButton._extendedKeys = this._extendedKeyboard;
    }

    _getKeyval(key) {
        let unicode = key.length ? key.charCodeAt(0) : undefined;
        return Clutter.unicode_to_keysym(unicode);
    }

    _press(key) {
        this.emit('activated');

        if (this._extendedKeys.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._pressTimeoutId = 0;

                    this.emit('long-press');

                    if (this._extendedKeys.length > 0) {
                        this._touchPressSlot = null;
                        this._ensureExtendedKeysPopup();
                        this.keyButton.set_hover(false);
                        this.keyButton.fake_release();
                        this._showSubkeys();
                    }

                    return GLib.SOURCE_REMOVE;
                });
        }
    }

    _release(key) {
        if (this._pressTimeoutId != 0) {
            GLib.source_remove(this._pressTimeoutId);
            this._pressTimeoutId = 0;
        }

        if (this._extendedKeys.length > 0)
            this.emit('pressed', this._getKeyval(key), key);

        this.emit('released', this._getKeyval(key), key);
        this._hideSubkeys();
    }

    cancel() {
        if (this._pressTimeoutId != 0) {
            GLib.source_remove(this._pressTimeoutId);
            this._pressTimeoutId = 0;
        }
        this._touchPressSlot = null;
        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;
        const targetActor = global.stage.get_event_actor(event);

        if (targetActor === this._boxPointer.bin ||
            this._boxPointer.bin.contains(targetActor))
            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);
        global.stage.connectObject(
            'captured-event', this._onCapturedEvent.bind(this), this);
        this.keyButton.connectObject('notify::mapped', () => {
            if (!this.keyButton.is_mapped())
                this._hideSubkeys();
        }, this);
    }

    _hideSubkeys() {
        if (this._boxPointer)
            this._boxPointer.close(BoxPointer.PopupAnimation.FULL);
        global.stage.disconnectObject(this);
        this.keyButton.disconnectObject(this);
        this._capturedPress = false;
    }

    _makeKey(key, icon) {
        let button = new St.Button({
            style_class: 'keyboard-key',
            x_expand: true,
        });

        if (icon) {
            let child = new St.Icon({ icon_name: icon });
            button.set_child(child);
            this._icon = child;
        } else {
            let label = GLib.markup_escape_text(key, -1);
            button.set_label(label);
        }

        button.keyWidth = 1;
        button.connect('button-press-event', () => {
            this._press(key);
            button.add_style_pseudo_class('active');
            return Clutter.EVENT_STOP;
        });
        button.connect('button-release-event', () => {
            this._release(key);
            button.remove_style_pseudo_class('active');
            return Clutter.EVENT_STOP;
        });
        button.connect('touch-event', (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;

            const slot = event.get_event_sequence().get_slot();

            if (!this._touchPressSlot &&
                event.type() == Clutter.EventType.TOUCH_BEGIN) {
                this._touchPressSlot = slot;
                this._press(key);
                button.add_style_pseudo_class('active');
            } else if (event.type() === Clutter.EventType.TOUCH_END) {
                if (!this._touchPressSlot ||
                    this._touchPressSlot === slot) {
                    this._release(key);
                    button.remove_style_pseudo_class('active');
                }

                if (this._touchPressSlot === slot)
                    this._touchPressSlot = null;
            }
            return Clutter.EVENT_STOP;
        });

        return button;
    }

    _getExtendedKeys() {
        this._extendedKeyboard = new St.BoxLayout({
            style_class: 'key-container',
            vertical: false,
        });
        for (let i = 0; i < this._extendedKeys.length; ++i) {
            let extendedKey = this._extendedKeys[i];
            let key = this._makeKey(extendedKey);

            key.extendedKey = extendedKey;
            this._extendedKeyboard.add(key);

            key.set_size(...this.keyButton.allocation.get_size());
            this.keyButton.connect('notify::allocation',
                () => key.set_size(...this.keyButton.allocation.get_size()));
        }
        this._boxPointer.bin.add_actor(this._extendedKeyboard);
    }

    get subkeys() {
        return this._boxPointer;
    }

    setWidth(width) {
        this.keyButton.keyWidth = width;
    }

    setLatched(latched) {
        if (!this._icon)
            return;

        if (latched) {
            this.keyButton.add_style_pseudo_class('latched');
            this._icon.icon_name = 'keyboard-caps-lock-symbolic';
        } else {
            this.keyButton.remove_style_pseudo_class('latched');
            this._icon.icon_name = 'keyboard-shift-symbolic';
        }
    }
});

var KeyboardModel = class {
    constructor(groupName) {
        let names = [groupName];
        if (groupName.includes('+'))
            names.push(groupName.replace(/\+.*/, ''));
        names.push('us');

        for (let i = 0; i < names.length; i++) {
            try {
                this._model = this._loadModel(names[i]);
                break;
            } catch (e) {
            }
        }
    }

    _loadModel(groupName) {
        const file = Gio.File.new_for_uri(
            `resource:///org/gnome/shell/osk-layouts/${groupName}.json`);
        let [success_, contents] = file.load_contents(null);

        const decoder = new TextDecoder();
        return JSON.parse(decoder.decode(contents));
    }

    getLevels() {
        return this._model.levels;
    }

    getKeysForLevel(levelName) {
        return this._model.levels.find(level => level == levelName);
    }
};

var FocusTracker = class {
    constructor() {
        this._rect = null;

        global.display.connectObject(
            'notify::focus-window', () => {
                this._setCurrentWindow(global.display.focus_window);
                this.emit('window-changed', this._currentWindow);
            },
            'grab-op-begin', (display, window, op) => {
                if (window === this._currentWindow &&
                    (op === Meta.GrabOp.MOVING || op === Meta.GrabOp.KEYBOARD_MOVING))
                    this.emit('window-grabbed');
            }, this);

        this._setCurrentWindow(global.display.focus_window);

        /* Valid for wayland clients */
        Main.inputMethod.connectObject('cursor-location-changed',
            (o, rect) => this._setCurrentRect(rect), this);

        this._ibusManager = IBusManager.getIBusManager();
        this._ibusManager.connectObject(
            'set-cursor-location', (manager, rect) => {
                /* Valid for X11 clients only */
                if (Main.inputMethod.currentFocus)
                    return;

                const grapheneRect = new Graphene.Rect();
                grapheneRect.init(rect.x, rect.y, rect.width, rect.height);

                this._setCurrentRect(grapheneRect);
            },
            'focus-in', () => this.emit('focus-changed', true),
            'focus-out', () => this.emit('focus-changed', false),
            this);
    }

    destroy() {
        this._currentWindow?.disconnectObject(this);
        global.display.disconnectObject(this);
        Main.inputMethod.disconnectObject(this);
        this._ibusManager.disconnectObject(this);
    }

    get currentWindow() {
        return this._currentWindow;
    }

    _setCurrentWindow(window) {
        this._currentWindow?.disconnectObject(this);

        this._currentWindow = window;

        if (this._currentWindow) {
            this._currentWindow.connectObject(
                'position-changed', () => this.emit('window-moved'), this);
        }
    }

    _setCurrentRect(rect) {
        // Some clients give us 0-sized rects, in that case set size to 1
        if (rect.size.width <= 0)
            rect.size.width = 1;
        if (rect.size.height <= 0)
            rect.size.height = 1;

        if (this._currentWindow) {
            const frameRect = this._currentWindow.get_frame_rect();
            const grapheneFrameRect = new Graphene.Rect();
            grapheneFrameRect.init(frameRect.x, frameRect.y,
                frameRect.width, frameRect.height);

            const rectInsideFrameRect = grapheneFrameRect.intersection(rect)[0];
            if (!rectInsideFrameRect)
                return;
        }

        if (this._rect && this._rect.equal(rect))
            return;

        this._rect = rect;
        this.emit('position-changed');
    }

    getCurrentRect() {
        const rect = {
            x: this._rect.origin.x,
            y: this._rect.origin.y,
            width: this._rect.size.width,
            height: this._rect.size.height,
        };

        return rect;
    }
};
Signals.addSignalMethods(FocusTracker.prototype);

var EmojiPager = GObject.registerClass({
    Properties: {
        'delta': GObject.ParamSpec.int(
            'delta', 'delta', 'delta',
            GObject.ParamFlags.READWRITE,
            GLib.MININT32, GLib.MAXINT32, 0),
    },
    Signals: {
        'emoji': { param_types: [GObject.TYPE_STRING] },
        'page-changed': {
            param_types: [GObject.TYPE_INT, GObject.TYPE_INT, GObject.TYPE_INT],
        },
    },
}, class EmojiPager extends St.Widget {
    _init(sections, nCols, nRows) {
        super._init({
            layout_manager: new Clutter.BinLayout(),
            reactive: true,
            clip_to_allocation: true,
            y_expand: 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.add_action_full('pan', Clutter.EventPhase.CAPTURE, panAction);
    }

    get delta() {
        return this._delta;
    }

    set delta(value) {
        if (value > this._width)
            value = this._width;
        else if (value < -this._width)
            value = -this._width;

        if (this._delta == value)
            return;

        this._delta = value;
        this.notify('delta');

        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.add_child(this._followingPanel);
                this.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 += dx;

        if (this._currentKey != null) {
            this._currentKey.cancel();
            this._currentKey = null;
        }

        return false;
    }

    _onPanBegin() {
        this._width = this.width;
        return true;
    }

    _onPanEnd() {
        if (Math.abs(this._delta) < this.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);

            this.remove_all_transitions();
            this.ease_property('delta', value, {
                duration: time,
                onComplete: () => {
                    this.setCurrentPage(this.getFollowingPage());
                },
            });
        }
    }

    _onPanCancel() {
        let relDelta = Math.abs(this._delta) / this.width;
        let time = PANEL_SWITCH_ANIMATION_TIME * Math.abs(relDelta);

        this.remove_all_transitions();
        this.ease_property('delta', 0, {
            duration: time,
        });
    }

    _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) {
        const gridLayout = new Clutter.GridLayout({
            orientation: Clutter.Orientation.HORIZONTAL,
            column_homogeneous: true,
            row_homogeneous: true,
        });
        const 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, 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.add_child(this._panel);
        }

        let page = this._pages[nPage];
        this.emit('page-changed', page.section.label, 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;
            }
        }
    }
});

var EmojiSelection = GObject.registerClass({
    Signals: {
        'emoji-selected': { param_types: [GObject.TYPE_STRING] },
        'close-request': {},
        'toggle': {},
    },
}, class EmojiSelection extends St.BoxLayout {
    _init() {
        super._init({
            style_class: 'emoji-panel',
            x_expand: true,
            y_expand: true,
            vertical: true,
        });

        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._emojiPager = new EmojiPager(this._sections, 11, 3);
        this._emojiPager.connect('page-changed', (pager, sectionLabel, page, nPages) => {
            this._onPageChanged(sectionLabel, page, nPages);
        });
        this._emojiPager.connect('emoji', (pager, str) => {
            this.emit('emoji-selected', str);
        });
        this.add_child(this._emojiPager);

        this._pageIndicator = new PageIndicators.PageIndicators(
            Clutter.Orientation.HORIZONTAL);
        this.add_child(this._pageIndicator);
        this._pageIndicator.setReactive(false);

        this._emojiPager.connect('notify::delta', () => {
            this._updateIndicatorPosition();
        });

        let bottomRow = this._createBottomRow();
        this.add_child(bottomRow);

        this._curPage = 0;
    }

    vfunc_map() {
        this._emojiPager.setCurrentPage(0);
        super.vfunc_map();
    }

    _onPageChanged(sectionLabel, page, nPages) {
        this._curPage = page;
        this._pageIndicator.setNPages(nPages);
        this._updateIndicatorPosition();

        for (let i = 0; i < this._sections.length; i++) {
            let sect = this._sections[i];
            sect.button.setLatched(sectionLabel == sect.label);
        }
    }

    _updateIndicatorPosition() {
        this._pageIndicator.setCurrentPosition(this._curPage -
            this._emojiPager.delta / this._emojiPager.width);
    }

    _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);

        let emoji = JSON.parse(new TextDecoder().decode(contents));

        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, 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);

            section.button = key;
        }

        key = new Key(null, [], 'go-down-symbolic');
        key.keyButton.add_style_class_name('default-key');
        key.keyButton.add_style_class_name('hide-key');
        key.connect('released', () => {
            this.emit('close-request');
        });
        row.appendKey(key);
        row.layoutButtons();

        const 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;
    }
});

var Keypad = GObject.registerClass({
    Signals: {
        'keyval': { param_types: [GObject.TYPE_UINT] },
    },
}, class Keypad extends AspectContainer {
    _init() {
        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 },
            { keyval: Clutter.KEY_BackSpace, icon: 'edit-clear-symbolic', left: 3, top: 0 },
            { keyval: Clutter.KEY_Return, extraClassName: 'enter-key', icon: 'keyboard-enter-symbolic', left: 3, top: 1, height: 2 },
        ];

        super._init({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });

        const 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.add_child(this._box);

        for (let i = 0; i < keys.length; i++) {
            let cur = keys[i];
            let key = new Key(cur.label || "", [], cur.icon);

            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, cur.left, cur.top, w, h);

            key.connect('released', () => {
                this.emit('keyval', cur.keyval);
            });
        }
    }
});

var KeyboardManager = class KeyBoardManager {
    constructor() {
        this._keyboard = null;
        this._a11yApplicationsSettings = new Gio.Settings({ schema_id: A11Y_APPLICATIONS_SCHEMA });
        this._a11yApplicationsSettings.connect('changed', this._syncEnabled.bind(this));

        this._seat = Clutter.get_default_backend().get_default_seat();
        this._seat.connect('notify::touch-mode', this._syncEnabled.bind(this));

        this._lastDevice = null;
        global.backend.connect('last-device-changed', (backend, device) => {
            if (device.device_type === Clutter.InputDeviceType.KEYBOARD_DEVICE)
                return;

            this._lastDevice = device;
            this._syncEnabled();
        });

        const mode = Shell.ActionMode.ALL & ~Shell.ActionMode.LOCK_SCREEN;
        const bottomDragAction = new EdgeDragAction.EdgeDragAction(St.Side.BOTTOM, mode);
        bottomDragAction.connect('activated', () => {
            if (this._keyboard)
                this._keyboard.gestureActivate(Main.layoutManager.bottomIndex);
        });
        bottomDragAction.connect('progress', (_action, progress) => {
            if (this._keyboard)
                this._keyboard.gestureProgress(progress);
        });
        bottomDragAction.connect('gesture-cancel', () => {
            if (this._keyboard)
                this._keyboard.gestureCancel();
        });
        global.stage.add_action_full('osk', Clutter.EventPhase.CAPTURE, bottomDragAction);
        this._bottomDragAction = bottomDragAction;

        this._syncEnabled();
    }

    _lastDeviceIsTouchscreen() {
        if (!this._lastDevice)
            return false;

        let deviceType = this._lastDevice.get_device_type();
        return deviceType == Clutter.InputDeviceType.TOUCHSCREEN_DEVICE;
    }

    _syncEnabled() {
        let enableKeyboard = this._a11yApplicationsSettings.get_boolean(SHOW_KEYBOARD);
        let autoEnabled = this._seat.get_touch_mode() && this._lastDeviceIsTouchscreen();
        let enabled = enableKeyboard || autoEnabled;

        if (!enabled && !this._keyboard)
            return;

        if (enabled && !this._keyboard) {
            this._keyboard = new Keyboard();
            this._keyboard.connect('visibility-changed', () => {
                this._bottomDragAction.enabled = !this._keyboard.visible;
            });
        } else if (!enabled && this._keyboard) {
            this._keyboard.setCursorLocation(null);
            this._keyboard.destroy();
            this._keyboard = null;
            this._bottomDragAction.enabled = true;
        }
    }

    get keyboardActor() {
        return this._keyboard;
    }

    get visible() {
        return this._keyboard && this._keyboard.visible;
    }

    open(monitor) {
        Main.layoutManager.keyboardIndex = monitor;

        if (this._keyboard)
            this._keyboard.open();
    }

    close() {
        if (this._keyboard)
            this._keyboard.close();
    }

    addSuggestion(text, callback) {
        if (this._keyboard)
            this._keyboard.addSuggestion(text, callback);
    }

    resetSuggestions() {
        if (this._keyboard)
            this._keyboard.resetSuggestions();
    }

    setSuggestionsVisible(visible) {
        this._keyboard?.setSuggestionsVisible(visible);
    }

    maybeHandleEvent(event) {
        if (!this._keyboard)
            return false;

        const actor = global.stage.get_event_actor(event);

        if (Main.layoutManager.keyboardBox.contains(actor) ||
            !!actor._extendedKeys || !!actor.extendedKey) {
            actor.event(event, true);
            actor.event(event, false);
            return true;
        }

        return false;
    }
};

var Keyboard = GObject.registerClass({
    Signals: {
        'visibility-changed': {},
    },
}, class Keyboard extends St.BoxLayout {
    _init() {
        super._init({ name: 'keyboard', reactive: true, vertical: true });
        this._focusInExtendedKeys = false;
        this._emojiActive = false;

        this._languagePopup = null;
        this._focusWindow = null;
        this._focusWindowStartY = null;

        this._latched = false; // current level is latched

        this._suggestions = null;
        this._emojiKeyVisible = Meta.is_wayland_compositor();

        this._focusTracker = new FocusTracker();
        this._focusTracker.connectObject(
            'position-changed', this._onFocusPositionChanged.bind(this),
            'window-grabbed', this._onFocusWindowMoving.bind(this), this);

        this._windowMovedId = this._focusTracker.connect('window-moved',
            this._onFocusWindowMoving.bind(this));

        // Valid only for X11
        if (!Meta.is_wayland_compositor()) {
            this._focusTracker.connectObject('focus-changed', (_tracker, focused) => {
                if (focused)
                    this.open(Main.layoutManager.focusIndex);
                else
                    this.close();
            }, this);
        }

        this._showIdleId = 0;

        this._keyboardVisible = false;
        this._keyboardRequested = false;
        this._keyboardRestingId = 0;

        Main.layoutManager.connectObject('monitors-changed',
            this._relayout.bind(this), this);

        this._setupKeyboard();

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

    get visible() {
        return this._keyboardVisible && super.visible;
    }

    set visible(visible) {
        super.visible = visible;
    }

    _onFocusPositionChanged(focusTracker) {
        let rect = focusTracker.getCurrentRect();
        this.setCursorLocation(focusTracker.currentWindow, rect.x, rect.y, rect.width, rect.height);
    }

    _onDestroy() {
        if (this._windowMovedId) {
            this._focusTracker.disconnect(this._windowMovedId);
            delete this._windowMovedId;
        }

        if (this._focusTracker) {
            this._focusTracker.destroy();
            delete this._focusTracker;
        }

        this._clearShowIdle();

        this._keyboardController.destroy();

        Main.layoutManager.untrackChrome(this);
        Main.layoutManager.keyboardBox.remove_actor(this);
        Main.layoutManager.keyboardBox.hide();

        if (this._languagePopup) {
            this._languagePopup.destroy();
            this._languagePopup = null;
        }
    }

    _setupKeyboard() {
        Main.layoutManager.keyboardBox.add_actor(this);
        Main.layoutManager.trackChrome(this);

        this._keyboardController = new KeyboardController();

        this._groups = {};
        this._currentPage = null;

        this._suggestions = new Suggestions();
        this.add_child(this._suggestions);

        this._aspectContainer = new AspectContainer({
            layout_manager: new Clutter.BinLayout(),
            y_expand: true,
        });
        this.add_child(this._aspectContainer);

        this._emojiSelection = new EmojiSelection();
        this._emojiSelection.connect('toggle', this._toggleEmoji.bind(this));
        this._emojiSelection.connect('close-request', () => this.close());
        this._emojiSelection.connect('emoji-selected', (selection, emoji) => {
            this._keyboardController.commitString(emoji);
        });

        this._aspectContainer.add_child(this._emojiSelection);
        this._emojiSelection.hide();

        this._keypad = new Keypad();
        this._keypad.connectObject('keyval', (_keypad, keyval) => {
            this._keyboardController.keyvalPress(keyval);
            this._keyboardController.keyvalRelease(keyval);
        }, this);
        this._aspectContainer.add_child(this._keypad);
        this._keypad.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.text_direction = Clutter.TextDirection.LTR;

        this._keyboardController.connectObject(
            'active-group', this._onGroupChanged.bind(this),
            'groups-changed', this._onKeyboardGroupsChanged.bind(this),
            'panel-state', this._onKeyboardStateChanged.bind(this),
            'keypad-visible', this._onKeypadVisible.bind(this),
            this);
        global.stage.connectObject('notify::key-focus',
            this._onKeyFocusChanged.bind(this), this);

        if (Meta.is_wayland_compositor()) {
            this._keyboardController.connectObject('emoji-visible',
                this._onEmojiKeyVisible.bind(this), 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._extendedKeys || focus.extendedKey);
        if (this._focusInExtendedKeys || extendedKeysWereFocused)
            return;

        if (!(focus instanceof Clutter.Text)) {
            this.close();
            return;
        }

        if (!this._showIdleId) {
            this._showIdleId = GLib.idle_add(GLib.PRIORITY_DEFAULT_IDLE, () => {
                this.open(Main.layoutManager.focusIndex);
                this._showIdleId = 0;
                return GLib.SOURCE_REMOVE;
            });
            GLib.Source.set_name_by_id(this._showIdleId, '[gnome-shell] this.open');
        }
    }

    _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, button.keyButton.keyWidth);
        }
    }

    _popupLanguageMenu(keyActor) {
        if (this._languagePopup)
            this._languagePopup.destroy();

        this._languagePopup = new LanguageSelectionPopup(keyActor);
        Main.layoutManager.addTopChrome(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;
            let icon = key.icon;

            /* Skip emoji button if necessary */
            if (!this._emojiKeyVisible && action == 'emoji')
                continue;

            extraButton = new Key(key.label || '', [], icon);

            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.close();
                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._currentPage, 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.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, extraButton.keyButton.keyWidth);
        }
    }

    _updateCurrentPageVisible() {
        if (this._currentPage)
            this._currentPage.visible = !this._emojiActive && !this._keypadVisible;
    }

    _setEmojiActive(active) {
        this._emojiActive = active;
        this._emojiSelection.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) {
        /* 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._currentPage.get_children();
        numOfVertSlots = rows.length;

        for (let i = 0; i < rows.length; ++i) {
            let keyboardRow = rows[i];
            let keys = keyboardRow.get_children();

            numOfHorizSlots = Math.max(numOfHorizSlots, keys.length);
        }

        return [numOfHorizSlots, numOfVertSlots];
    }

    _relayout() {
        let monitor = Main.layoutManager.keyboardMonitor;

        if (!monitor)
            return;

        let maxHeight = monitor.height / 3;
        this.width = monitor.width;

        if (monitor.width > monitor.height) {
            this.height = maxHeight;
        } else {
            /* In portrait mode, lack of horizontal space means we won't be
             * able to make the OSK that big while keeping size ratio, so
             * we allow the OSK being smaller than 1/3rd of the monitor height
             * there.
             */
            this.height = -1;
            const forWidth = this.get_theme_node().adjust_for_width(monitor.width);
            const [, natHeight] = this.get_preferred_height(forWidth);
            this.height = Math.min(maxHeight, natHeight);
        }
    }

    _onGroupChanged() {
        this._ensureKeysForGroup(this._keyboardController.getCurrentGroup());
        this._setActiveLayer(0);
    }

    _onKeyboardGroupsChanged() {
        let nonGroupActors = [this._emojiSelection, this._keypad];
        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.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.open(Main.layoutManager.focusIndex);
        else
            this.close();
    }

    _setActiveLayer(activeLevel) {
        let activeGroupName = this._keyboardController.getCurrentGroup();
        let layers = this._groups[activeGroupName];
        let currentPage = layers[activeLevel];

        if (this._currentPage == currentPage) {
            this._updateCurrentPageVisible();
            return;
        }

        if (this._currentPage != null) {
            this._setCurrentLevelLatched(this._currentPage, false);
            this._currentPage.disconnect(this._currentPage._destroyID);
            this._currentPage.hide();
            delete this._currentPage._destroyID;
        }

        this._currentPage = currentPage;
        this._currentPage._destroyID = this._currentPage.connect('destroy', () => {
            this._currentPage = null;
        });
        this._updateCurrentPageVisible();
    }

    _clearKeyboardRestTimer() {
        if (!this._keyboardRestingId)
            return;
        GLib.source_remove(this._keyboardRestingId);
        this._keyboardRestingId = 0;
    }

    open(immediate = false) {
        this._clearShowIdle();
        this._keyboardRequested = true;

        if (this._keyboardVisible) {
            this._relayout();
            return;
        }

        this._clearKeyboardRestTimer();

        if (immediate) {
            this._open();
            return;
        }

        this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
            KEYBOARD_REST_TIME,
            () => {
                this._clearKeyboardRestTimer();
                this._open();
                return GLib.SOURCE_REMOVE;
            });
        GLib.Source.set_name_by_id(this._keyboardRestingId, '[gnome-shell] this._clearKeyboardRestTimer');
    }

    _open() {
        if (!this._keyboardRequested)
            return;

        this._relayout();
        this._animateShow();

        this._setEmojiActive(false);
    }

    close(immediate = false) {
        this._clearShowIdle();
        this._keyboardRequested = false;

        if (!this._keyboardVisible)
            return;

        this._clearKeyboardRestTimer();

        if (immediate) {
            this._close();
            return;
        }

        this._keyboardRestingId = GLib.timeout_add(GLib.PRIORITY_DEFAULT,
            KEYBOARD_REST_TIME,
            () => {
                this._clearKeyboardRestTimer();
                this._close();
                return GLib.SOURCE_REMOVE;
            });
        GLib.Source.set_name_by_id(this._keyboardRestingId, '[gnome-shell] this._clearKeyboardRestTimer');
    }

    _close() {
        if (this._keyboardRequested)
            return;

        this._animateHide();
        this.setCursorLocation(null);
    }

    _animateShow() {
        if (this._focusWindow)
            this._animateWindow(this._focusWindow, true);

        Main.layoutManager.keyboardBox.show();
        this.ease({
            translation_y: -this.height,
            opacity: 255,
            duration: KEYBOARD_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._animateShowComplete();
            },
        });
        this._keyboardVisible = true;
        this.emit('visibility-changed');
    }

    _animateShowComplete() {
        let keyboardBox = Main.layoutManager.keyboardBox;
        this._keyboardHeightNotifyId = keyboardBox.connect('notify::height', () => {
            this.translation_y = -this.height;
        });

        // Toggle visibility so the keyboardBox can update its chrome region.
        if (!Meta.is_wayland_compositor()) {
            keyboardBox.hide();
            keyboardBox.show();
        }
    }

    _animateHide() {
        if (this._focusWindow)
            this._animateWindow(this._focusWindow, false);

        if (this._keyboardHeightNotifyId) {
            Main.layoutManager.keyboardBox.disconnect(this._keyboardHeightNotifyId);
            this._keyboardHeightNotifyId = 0;
        }
        this.ease({
            translation_y: 0,
            opacity: 0,
            duration: KEYBOARD_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_IN_QUAD,
            onComplete: () => {
                this._animateHideComplete();
            },
        });

        this._keyboardVisible = false;
        this.emit('visibility-changed');
    }

    _animateHideComplete() {
        Main.layoutManager.keyboardBox.hide();
    }

    gestureProgress(delta) {
        this._gestureInProgress = true;
        Main.layoutManager.keyboardBox.show();
        let progress = Math.min(delta, this.height) / this.height;
        this.translation_y = -this.height * progress;
        this.opacity = 255 * progress;
        const windowActor = this._focusWindow?.get_compositor_private();
        if (windowActor)
            windowActor.y = this._focusWindowStartY - (this.height * progress);
    }

    gestureActivate() {
        this.open(true);
        this._gestureInProgress = false;
    }

    gestureCancel() {
        if (this._gestureInProgress)
            this._animateHide();
        this._gestureInProgress = false;
    }

    resetSuggestions() {
        if (this._suggestions)
            this._suggestions.clear();
    }

    setSuggestionsVisible(visible) {
        this._suggestions?.setVisible(visible);
    }

    addSuggestion(text, callback) {
        if (!this._suggestions)
            return;
        this._suggestions.add(text, callback);
        this._suggestions.show();
    }

    _clearShowIdle() {
        if (!this._showIdleId)
            return;
        GLib.source_remove(this._showIdleId);
        this._showIdleId = 0;
    }

    _windowSlideAnimationComplete(window, finalY) {
        // Synchronize window positions again.
        const frameRect = window.get_frame_rect();
        const bufferRect = window.get_buffer_rect();

        finalY += frameRect.y - bufferRect.y;

        frameRect.y = finalY;

        this._focusTracker.disconnect(this._windowMovedId);
        window.move_frame(true, frameRect.x, frameRect.y);
        this._windowMovedId = this._focusTracker.connect('window-moved',
            this._onFocusWindowMoving.bind(this));
    }

    _animateWindow(window, show) {
        let windowActor = window.get_compositor_private();
        if (!windowActor)
            return;

        const finalY = show
            ? this._focusWindowStartY - Main.layoutManager.keyboardBox.height
            : this._focusWindowStartY;

        windowActor.ease({
            y: finalY,
            duration: KEYBOARD_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => {
                windowActor.y = finalY;
                this._windowSlideAnimationComplete(window, finalY);
            },
        });
    }

    _onFocusWindowMoving() {
        if (this._focusTracker.currentWindow === this._focusWindow) {
            // Don't use _setFocusWindow() here because that would move the
            // window while the user has grabbed it. Instead we simply "let go"
            // of the window.
            this._focusWindow = null;
            this._focusWindowStartY = null;
        }

        this.close(true);
    }

    _setFocusWindow(window) {
        if (this._focusWindow === window)
            return;

        if (this._keyboardVisible && this._focusWindow)
            this._animateWindow(this._focusWindow, false);

        const windowActor = window?.get_compositor_private();
        windowActor?.remove_transition('y');
        this._focusWindowStartY = windowActor ? windowActor.y : null;

        if (this._keyboardVisible && window)
            this._animateWindow(window, true);

        this._focusWindow = window;
    }

    setCursorLocation(window, x, y, w, h) {
        let monitor = Main.layoutManager.keyboardMonitor;

        if (window && monitor) {
            const keyboardHeight = Main.layoutManager.keyboardBox.height;
            const keyboardY1 = (monitor.y + monitor.height) - keyboardHeight;

            if (this._focusWindow === window) {
                if (y + h + keyboardHeight < keyboardY1)
                    this._setFocusWindow(null);

                return;
            }

            if (y + h >= keyboardY1)
                this._setFocusWindow(window);
            else
                this._setFocusWindow(null);
        } else {
            this._setFocusWindow(null);
        }
    }
});

var KeyboardController = class {
    constructor() {
        let seat = Clutter.get_default_backend().get_default_seat();
        this._virtualDevice = seat.create_virtual_device(Clutter.InputDeviceType.KEYBOARD_DEVICE);

        this._inputSourceManager = InputSourceManager.getInputSourceManager();
        this._inputSourceManager.connectObject(
            'current-source-changed', this._onSourceChanged.bind(this),
            'sources-changed', this._onSourcesModified.bind(this), this);
        this._currentSource = this._inputSourceManager.currentSource;

        Main.inputMethod.connectObject(
            'notify::content-purpose', this._onContentPurposeHintsChanged.bind(this),
            'notify::content-hints', this._onContentPurposeHintsChanged.bind(this),
            'input-panel-state', (o, state) => this.emit('panel-state', state), this);
    }

    destroy() {
        this._inputSourceManager.disconnectObject(this);
        Main.inputMethod.disconnectObject(this);

        // Make sure any buttons pressed by the virtual device are released
        // immediately instead of waiting for the next GC cycle
        this._virtualDevice.run_dispose();
    }

    _onSourcesModified() {
        this.emit('groups-changed');
    }

    _onSourceChanged(inputSourceManager, _oldSource) {
        let source = inputSourceManager.currentSource;
        this._currentSource = source;
        this.emit('active-group', source.id);
    }

    _onContentPurposeHintsChanged(method) {
        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() * 1000,
                                          keyval, Clutter.KeyState.PRESSED);
    }

    keyvalRelease(keyval) {
        this._virtualDevice.notify_keyval(Clutter.get_current_event_time() * 1000,
                                          keyval, Clutter.KeyState.RELEASED);
    }
};
Signals.addSignalMethods(KeyboardController.prototype);
(uuay)panelMenu.jsh// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Button, SystemIndicator */

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

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',
            x_expand: true,
            y_expand: true,
        }, true);

        super._init(params);

        this._delegate = this;

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

        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) {
        this.set_allocation(box);

        let child = this.get_first_child();
        if (!child)
            return;

        let [, 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);
    }

    _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 ?? '',
            accessible_role: Atk.Role.MENU,
        });

        if (dontCreateMenu)
            this.menu = new PopupMenu.PopupDummyMenu(this);
        else
            this.setMenu(new PopupMenu.PopupMenu(this, 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');
    }

    vfunc_event(event) {
        if (this.menu &&
            (event.type() == Clutter.EventType.TOUCH_BEGIN ||
             event.type() == Clutter.EventType.BUTTON_PRESS))
            this.menu.toggle();

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_hide() {
        super.vfunc_hide();

        if (this.menu)
            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);
            if (group) {
                let direction = symbol == Clutter.KEY_Left ? St.DirectionType.LEFT : St.DirectionType.RIGHT;
                group.navigate_focus(this, direction, false);
                return Clutter.EVENT_STOP;
            }
        }
        return Clutter.EVENT_PROPAGATE;
    }

    _onOpenStateChanged(menu, open) {
        if (open)
            this.add_style_pseudo_class('active');
        else
            this.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: ${maxHeight}px;`;
    }

    _onDestroy() {
        if (this.menu)
            this.menu.destroy();
        super._onDestroy();
    }
});

/* 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 = GObject.registerClass(
class SystemIndicator extends St.BoxLayout {
    _init() {
        super._init({
            style_class: 'panel-status-indicators-box',
            reactive: true,
            visible: false,
        });
        this.menu = new PopupMenu.PopupMenuSection();
    }

    get indicators() {
        let klass = this.constructor.name;
        let { stack } = new Error();
        log(`Usage of indicator.indicators is deprecated for ${klass}\n${stack}`);
        return this;
    }

    _syncIndicatorsVisible() {
        this.visible = this.get_children().some(a => a.visible);
    }

    _addIndicator() {
        let icon = new St.Icon({ style_class: 'system-status-icon' });
        this.add_actor(icon);
        icon.connect('notify::visible', this._syncIndicatorsVisible.bind(this));
        this._syncIndicatorsVisible();
        return icon;
    }
});
(uuay)sessionMode.jsp// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported SessionMode, listModes */

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

const FileUtils = imports.misc.fileUtils;
const Params = imports.misc.params;

const Config = imports.misc.config;

const DEFAULT_MODE = 'restrictive';

const USER_SESSION_COMPONENTS = [
    'polkitAgent', 'telepathyClient', 'keyring',
    'autorunManager', 'automountManager',
];

if (Config.HAVE_NETWORKMANAGER)
    USER_SESSION_COMPONENTS.push('networkAgent');

const _modes = {
    'restrictive': {
        parentMode: null,
        stylesheetName: 'gnome-shell.css',
        themeResourceName: 'gnome-shell-theme.gresource',
        iconsResourceName: 'gnome-shell-icons.gresource',
        hasOverview: false,
        showCalendarEvents: false,
        showWelcomeDialog: false,
        allowSettings: false,
        allowScreencast: false,
        debugFlags: [],
        enabledExtensions: [],
        hasRunDialog: false,
        hasWorkspaces: false,
        hasWindows: false,
        hasNotifications: false,
        hasWmMenus: false,
        isLocked: false,
        isGreeter: false,
        isPrimary: false,
        unlockDialog: null,
        components: [],
        panel: {
            left: [],
            center: [],
            right: [],
        },
        panelStyle: null,
    },

    'gdm': {
        hasNotifications: true,
        stylesheetName: 'gdm.css',
        themeResourceName: 'gdm-theme.gresource',
        isGreeter: true,
        isPrimary: true,
        unlockDialog: imports.gdm.loginDialog.LoginDialog,
        components: Config.HAVE_NETWORKMANAGER
            ? ['networkAgent', 'polkitAgent']
            : ['polkitAgent'],
        panel: {
            left: [],
            center: ['dateMenu'],
            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu'],
        },
        panelStyle: 'login-screen',
    },

    'unlock-dialog': {
        isLocked: true,
        unlockDialog: undefined,
        components: ['polkitAgent', 'telepathyClient'],
        panel: {
            left: [],
            center: [],
            right: ['dwellClick', 'a11y', 'keyboard', 'aggregateMenu'],
        },
        panelStyle: 'unlock-screen',
    },

    'user': {
        hasOverview: true,
        showCalendarEvents: true,
        showWelcomeDialog: true,
        allowSettings: true,
        allowScreencast: true,
        hasRunDialog: true,
        hasWorkspaces: true,
        hasWindows: true,
        hasWmMenus: true,
        hasNotifications: true,
        isLocked: false,
        isPrimary: true,
        unlockDialog: imports.ui.unlockDialog.UnlockDialog,
        components: USER_SESSION_COMPONENTS,
        panel: {
            left: ['activities', 'appMenu'],
            center: ['dateMenu'],
            right: ['screenRecording', 'dwellClick', '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 (Object.prototype.hasOwnProperty.call(_modes, modeName))
        return;

    let fileContent, success_, newMode;
    try {
        [success_, fileContent] = file.load_contents(null);
        const decoder = new TextDecoder();
        newMode = JSON.parse(decoder.decode(fileContent));
    } catch (e) {
        return;
    }

    _modes[modeName] = {};
    const  excludedProps = ['unlockDialog'];
    for (let prop in _modes[DEFAULT_MODE]) {
        if (newMode[prop] !== undefined &&
            !excludedProps.includes(prop))
            _modes[modeName][prop] = newMode[prop];
    }
    _modes[modeName]['isPrimary'] = true;
}

function _loadModes() {
    FileUtils.collectFromDatadirs('modes', false, _loadMode);
}

function listModes() {
    _loadModes();
    let loop = new GLib.MainLoop(null, false);
    let id = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
        let names = Object.getOwnPropertyNames(_modes);
        for (let i = 0; i < names.length; i++) {
            if (_modes[names[i]].isPrimary)
                print(names[i]);
        }
        loop.quit();
    });
    GLib.Source.set_name_by_id(id, '[gnome-shell] listModes');
    loop.run();
}

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) {
        console.debug(`sessionMode: Pushing mode ${mode}`);
        this._modeStack.push(mode);
        this._sync();
    }

    popMode(mode) {
        if (this.currentMode != mode || this._modeStack.length === 1)
            throw new Error("Invalid SessionMode.popMode");

        console.debug(`sessionMode: Popping mode ${mode}`);
        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)hwtest.js�#/* exported run, script_desktopShown, script_overviewShowStart,
            script_overviewShowDone, script_applicationsShowStart,
            script_applicationsShowDone, script_mainViewDrawStart,
            script_mainViewDrawDone, script_overviewDrawStart,
            script_overviewDrawDone, script_redrawTestStart,
            script_redrawTestDone, script_collectTimings,
            script_geditLaunch, script_geditFirstFrame,
            clutter_stagePaintStart, clutter_paintCompletedTimestamp */
/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^clutter"] }] */
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() {
    const 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) { // eslint-disable-line no-constant-condition
        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;
}

async function run() {
    /* eslint-disable no-await-in-loop */
    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");

    await 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();
    await Scripting.waitLeisure();
    Scripting.scriptEvent('overviewShowDone');

    await Scripting.sleep(1000);

    Scripting.scriptEvent('applicationsShowStart');
    // eslint-disable-next-line require-atomic-updates
    Main.overview.dash.showAppsButton.checked = true;

    await Scripting.waitLeisure();
    Scripting.scriptEvent('applicationsShowDone');

    await Scripting.sleep(1000);

    Main.overview.hide();
    await Scripting.waitLeisure();

    // --------------------- //
    // Tests of redraw speed //
    // --------------------- //

    global.frame_timestamps = true;
    global.frame_finish_timestamp = true;

    for (let k = 0; k < 5; k++)
        await Scripting.createTestWindow({ maximized: true });
    await Scripting.waitTestWindows();

    await Scripting.sleep(1000);

    Scripting.scriptEvent('mainViewDrawStart');
    await waitAndDraw(1000);
    Scripting.scriptEvent('mainViewDrawDone');

    Main.overview.show();
    Scripting.waitLeisure();

    await Scripting.sleep(1500);

    Scripting.scriptEvent('overviewDrawStart');
    await waitAndDraw(1000);
    Scripting.scriptEvent('overviewDrawDone');

    await Scripting.destroyTestWindows();
    Main.overview.hide();

    await Scripting.createTestWindow({
        maximized: true,
        redraws: true,
    });
    await Scripting.waitTestWindows();

    await Scripting.sleep(1000);

    Scripting.scriptEvent('redrawTestStart');
    await Scripting.sleep(1000);
    Scripting.scriptEvent('redrawTestDone');

    await Scripting.sleep(1000);
    Scripting.scriptEvent('collectTimings');

    await Scripting.destroyTestWindows();

    global.frame_timestamps = false;
    global.frame_finish_timestamp = false;

    await 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) {
        await waitSignal(global.display, 'window-created');
        windows = app.get_windows();
    }

    let actor = windows[0].get_compositor_private();
    await waitSignal(actor, 'first-frame');
    Scripting.scriptEvent('geditFirstFrame');

    await Scripting.sleep(1000);

    windows[0].delete(global.get_current_time());

    await Scripting.sleep(1000);

    interfaceSettings.set_boolean('enable-animations', true);
    /* eslint-enable no-await-in-loop */
}

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)runDialog.js�!// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported RunDialog */

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

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

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 RunDialog = GObject.registerClass(
class RunDialog extends ModalDialog.ModalDialog {
    _init() {
        super._init({
            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': () => global.context.terminate(),

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

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

        let title = _('Run a Command');

        let content = new Dialog.MessageDialogContent({ title });
        this.contentLayout.add_actor(content);

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

        this._entryText = entry.clutter_text;
        content.add_child(entry);
        this.setInitialKeyFocus(this._entryText);

        let defaultDescriptionText = _('Press ESC to close');

        this._descriptionLabel = new St.Label({
            style_class: 'run-dialog-description',
            text: defaultDescriptionText,
        });
        content.add_child(this._descriptionLabel);

        this._commandError = false;

        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.KEY_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;
        });
        this._entryText.connect('text-changed', () => {
            this._descriptionLabel.set_text(defaultDescriptionText);
        });
    }

    vfunc_key_release_event(event) {
        if (event.keyval === Clutter.KEY_Escape) {
            this.close();
            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);
            }
            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.includes('/'))
            return this._pathCompleter.get_completion_suffix(text);
        else
            return this._getCommandCompletion(text);
    }

    _run(input, inTerminal) {
        input = this._history.addItem(input); // trims input
        let command = input;

        this._commandError = false;
        let f;
        if (this._enableInternalCommands)
            f = this._internalCommands[input];
        else
            f = null;
        if (f) {
            f();
        } else {
            try {
                if (inTerminal) {
                    let exec = this._terminalSettings.get_string(EXEC_KEY);
                    let execArg = this._terminalSettings.get_string(EXEC_ARG_KEY);
                    command = `${exec} ${execArg} ${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) {
                    if (input.charAt(0) == '~')
                        input = input.slice(1);
                    path = `${GLib.get_home_dir()}/${input}`;
                }

                if (path && 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 (err) {
                        // 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 = err.message.replace(/[^:]*: *(.+)/, '$1');
                        this._showError(message);
                    }
                } else {
                    this._showError(e.message);
                }
            }
        }
    }

    _showError(message) {
        this._commandError = true;
        this._descriptionLabel.set_text(message);
    }

    _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._entryText.set_text('');
        this._commandError = false;

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

        return super.open();
    }
});
(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 Signals = imports.signals;

const Background = imports.ui.background;
const FocusCaretTracker = imports.ui.focusCaretTracker;
const Main = imports.ui.main;
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';

var MouseSpriteContent = GObject.registerClass({
    Implements: [Clutter.Content],
}, class MouseSpriteContent extends GObject.Object {
    _init() {
        super._init();
        this._scale = 1.0;
        this._monitorScale = 1.0;
        this._texture = null;
    }

    vfunc_get_preferred_size() {
        if (!this._texture)
            return [false, 0, 0];

        let width = this._texture.get_width() / this._scale;
        let height = this._texture.get_height() / this._scale;

        return [true, width, height];
    }

    vfunc_paint_content(actor, node, _paintContext) {
        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());
    }

    _textureScale() {
        if (!this._texture)
            return 1;

        /* This is a workaround to guess the sprite scale; while it works file
         * in normal scenarios, it's not guaranteed to work in all the cases,
         * and so we should actually add an API to mutter that will allow us
         * to know the real spirte texture scaling in order to adapt it to the
         * wanted one. */
        let avgSize = (this._texture.get_width() + this._texture.get_height()) / 2;
        return Math.max (1, Math.floor (avgSize / Meta.prefs_get_cursor_size() + .1));
    }

    _recomputeScale() {
        let scale = this._textureScale() / this._monitorScale;

        if (this._scale != scale) {
            this._scale = scale;
            return true;
        }
        return false;
    }

    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._recomputeScale();
            this.invalidate_size();
        }
    }

    get scale() {
        return this._scale;
    }

    set monitorScale(monitorScale) {
        this._monitorScale = monitorScale;
        if (this._recomputeScale())
            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();

        // Create the first ZoomRegion and initialize it according to the
        // magnification settings.

        [this.xMouse, this.yMouse] = global.get_pointer();

        let aZoomRegion = new ZoomRegion(this, this._mouseSprite);
        this._zoomRegions.push(aZoomRegion);
        this._settingsInit(aZoomRegion);
        aZoomRegion.scrollContentsTo(this.xMouse, this.yMouse);

        this._updateContentScale();

        St.Settings.get().connect('notify::magnifier-active', () => {
            this.setActive(St.Settings.get().magnifier_active);
        });

        this.setActive(St.Settings.get().magnifier_active);
    }

    _updateContentScale() {
        let monitor = Main.layoutManager.findMonitorForPoint(this.xMouse,
                                                             this.yMouse);
        this._mouseSprite.content.monitorScale = monitor ?
                                                 monitor.geometry_scale : 1;
    }

    /**
     * showSystemCursor:
     * Show the system mouse pointer.
     */
    showSystemCursor() {
        const seat = Clutter.get_default_backend().get_default_seat();

        if (seat.is_unfocus_inhibited())
            seat.uninhibit_unfocus();

        if (this._cursorVisibilityChangedId) {
            this._cursorTracker.disconnect(this._cursorVisibilityChangedId);
            delete this._cursorVisibilityChangedId;

            this._cursorTracker.set_pointer_visible(true);
        }
    }

    /**
     * hideSystemCursor:
     * Hide the system mouse pointer.
     */
    hideSystemCursor() {
        const seat = Clutter.get_default_backend().get_default_seat();

        if (!seat.is_unfocus_inhibited())
            seat.inhibit_unfocus();

        if (!this._cursorVisibilityChangedId) {
            this._cursorTracker.set_pointer_visible(false);
            this._cursorVisibilityChangedId = this._cursorTracker.connect('visibility-changed', () => {
                if (this._cursorTracker.get_pointer_visible())
                    this._cursorTracker.set_pointer_visible(false);
            });
        }
    }

    /**
     * setActive:
     * Show/hide all the zoom regions.
     * @param {bool} activate: Boolean to activate or de-activate the magnifier.
     */
    setActive(activate) {
        let isActive = this.isActive();

        this._zoomRegions.forEach(zoomRegion => {
            zoomRegion.setActive(activate);
        });

        if (isActive === activate)
            return;

        if (activate) {
            this._updateMouseSprite();
            this._cursorTracker.connectObject(
                'cursor-changed', this._updateMouseSprite.bind(this), this);
            Meta.disable_unredirect_for_display(global.display);
            this.startTrackingMouse();
        } else {
            this._cursorTracker.disconnectObject(this);
            this._mouseSprite.content.texture = null;
            Meta.enable_unredirect_for_display(global.display);
            this.stopTrackingMouse();
        }

        if (this._crossHairs)
            this._crossHairs.setEnabled(activate);

        // Make sure system mouse pointer is shown when all zoom regions are
        // invisible.
        if (!activate)
            this.showSystemCursor();

        // Notify interested parties of this change
        this.emit('active-changed', activate);
    }

    /**
     * isActive:
     * @returns {bool} Whether the magnifier is active.
     */
    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 / 60;
            this._pointerWatch = PointerWatcher.getPointerWatcher().addWatch(interval, this.scrollToMousePos.bind(this));

            this.scrollToMousePos();
        }
    }

    /**
     * stopTrackingMouse:
     * Turn off mouse tracking, if not already doing so.
     */
    stopTrackingMouse() {
        if (this._pointerWatch)
            this._pointerWatch.remove();

        this._pointerWatch = null;
    }

    /**
     * isTrackingMouse:
     * @returns {bool} whether the magnifier is currently tracking the mouse
     */
    isTrackingMouse() {
        return !!this._mouseTrackingId;
    }

    /**
     * scrollToMousePos:
     * Position all zoom regions' ROI relative to the current location of the
     * system pointer.
     */
    scrollToMousePos(...args) {
        const [xMouse, yMouse] = args.length ? args : global.get_pointer();

        if (xMouse === this.xMouse && yMouse === this.yMouse)
            return;

        this.xMouse = xMouse;
        this.yMouse = yMouse;

        this._updateContentScale();

        let sysMouseOverAny = false;
        this._zoomRegions.forEach(zoomRegion => {
            if (zoomRegion.scrollToMousePos())
                sysMouseOverAny = true;
        });
        if (sysMouseOverAny)
            this.hideSystemCursor();
        else
            this.showSystemCursor();
    }

    /**
     * createZoomRegion:
     * Create a ZoomRegion instance with the given properties.
     * @param {number} 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.
     * @param {number} yMagFactor:
     *    The power to set the vertical magnification of the ZoomRegion.
     * @param {{x: number, y: number, width: number, height: number}} roi:
     *    The reg Object that defines the region to magnify, given in
     *    unmagnified coordinates.
     * @param {{x: number, y: number, width: number, height: number}} viewPort:
     *     Object that defines the position of the ZoomRegion on screen.
     * @returns {ZoomRegion} the newly created ZoomRegion.
     */
    createZoomRegion(xMagFactor, yMagFactor, roi, viewPort) {
        let zoomRegion = new ZoomRegion(this, this._mouseSprite);
        zoomRegion.setViewPort(viewPort);

        // We ignore the redundant width/height on the ROI
        let fixedROI = Object.create(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.
     * @param {ZoomRegion} 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.
     * @returns {number[]} The Magnifier's zoom region list.
     */
    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 => {
            zoomRegion.addCrosshairs(theCrossHairs);
        });
    }

    /**
     * setCrosshairsVisible:
     * Show or hide the cross hair.
     * @param {bool} visible: Flag that indicates show (true) or hide (false).
     */
    setCrosshairsVisible(visible) {
        if (visible) {
            if (!this._crossHairs)
                this.addCrosshairs();
            this._crossHairs.show();
        } else {
            // eslint-disable-next-line no-lonely-if
            if (this._crossHairs)
                this._crossHairs.hide();
        }
    }

    /**
     * setCrosshairsColor:
     * Set the color of the crosshairs for all ZoomRegions.
     * @param {string} 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.
     * @returns {string} 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.
     * @param {number} 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.
     * @returns {number} The width of the vertical and horizontal
     *     lines of the crosshairs.
     */
    getCrosshairsThickness() {
        if (this._crossHairs)
            return this._crossHairs.getThickness();
        else
            return 0;
    }

    /**
     * setCrosshairsOpacity:
     * @param {number} opacity: Value between 0.0 (transparent)
     *     and 1.0 (fully opaque).
     */
    setCrosshairsOpacity(opacity) {
        if (this._crossHairs)
            this._crossHairs.setOpacity(opacity * 255);
    }

    /**
     * getCrosshairsOpacity:
     * @returns {number} 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.
     * @param {number} length: The length of the vertical and horizontal
     *     lines making up the crosshairs.
     */
    setCrosshairsLength(length) {
        if (this._crossHairs) {
            let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
            this._crossHairs.setLength(length / scaleFactor);
        }
    }

    /**
     * getCrosshairsLength:
     * Get the crosshairs length.
     * @returns {number} 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.
     * @param {bool} clip: Flag to indicate whether to clip the crosshairs.
     */
    setCrosshairsClip(clip) {
        if (!this._crossHairs)
            return;

        // Setting no clipping on crosshairs means a zero sized clip rectangle.
        this._crossHairs.setClip(clip ? CROSSHAIRS_CLIP_SIZE : [0, 0]);
    }

    /**
     * getCrosshairsClip:
     * Get whether the crosshairs are clipped by the mouse image.
     * @returns {bool} 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({
            translation_x: -xHot,
            translation_y: -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 = global.backend.get_core_idle_monitor();
        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);
        }
    }

    _convertExtentsToScreenSpace(accessible, extents) {
        const toplevelWindowTypes = new Set([
            Atspi.Role.FRAME,
            Atspi.Role.DIALOG,
            Atspi.Role.WINDOW,
        ]);

        try {
            let app = null;
            let parentWindow = null;
            let iter = accessible;
            while (iter) {
                if (iter.get_role() === Atspi.Role.APPLICATION) {
                    app = iter;
                    /* This is the last Accessible we are interested in */
                    break;
                } else if (toplevelWindowTypes.has(iter.get_role())) {
                    parentWindow = iter;
                }
                iter = iter.get_parent();
            }

            /* We don't want to translate our own events to the focus window.
             * They are also already scaled by clutter before being sent, so
             * we don't need to do that here either. */
            if (app && app.get_name() === 'gnome-shell')
                return extents;

            /* Only events from the focused widget of the focused window. Some
             * widgets seem to claim to have focus when the window does not so
             * check both. */
            const windowActive = parentWindow &&
                parentWindow.get_state_set().contains(Atspi.StateType.ACTIVE);
            const accessibleFocused =
                accessible.get_state_set().contains(Atspi.StateType.FOCUSED);
            if (!windowActive || !accessibleFocused)
                return null;
        } catch (e) {
            throw new Error(`Failed to validate parent window: ${e}`);
        }

        const { focusWindow } = global.display;
        if (!focusWindow)
            return null;

        let windowRect = focusWindow.get_frame_rect();
        if (!focusWindow.is_client_decorated())
            windowRect = focusWindow.frame_rect_to_client_rect(windowRect);

        const scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
        const screenSpaceExtents = new Atspi.Rect({
            x: windowRect.x + (scaleFactor * extents.x),
            y: windowRect.y + (scaleFactor * extents.y),
            width: scaleFactor * extents.width,
            height: scaleFactor * extents.height,
        });

        return screenSpaceExtents;
    }

    _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.WINDOW);
            extents = this._convertExtentsToScreenSpace(event.source, extents);
            if (!extents)
                return;
        } catch (e) {
            log(`Failed to read extents of focused component: ${e.message}`);
            return;
        }

        const [xFocus, yFocus] = [
            extents.x + (extents.width / 2),
            extents.y + (extents.height / 2),
        ];

        if (this._xFocus !== xFocus || this._yFocus !== yFocus) {
            [this._xFocus, this._yFocus] = [xFocus, yFocus];
            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(),
                Atspi.CoordType.WINDOW);
            extents = this._convertExtentsToScreenSpace(text, extents);
            if (!extents)
                return;
        } catch (e) {
            log(`Failed to read extents of text caret: ${e.message}`);
            return;
        }

        const [xCaret, yCaret] = [extents.x, extents.y];

        // Ignore event(s) if the caret size is none (0x0). This happens a lot if
        // the cursor offset can't be translated into a location. This is a work
        // around.
        if (extents.width === 0 && extents.height === 0)
            return;

        if (this._xCaret !== xCaret || this._yCaret !== yCaret) {
            [this._xCaret, this._yCaret] = [xCaret, yCaret];
            this._centerFromCaretPosition();
        }
    }

    /**
     * setActive:
     * @param {bool} 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 {
            Main.uiGroup.set_opacity(255);
            this._disconnectSignals();
            this._destroyActors();
        }

        this._syncCaretTracking();
        this._syncFocusTracking();
    }

    /**
     * isActive:
     * @returns {bool} Whether this ZoomRegion is active
     */
    isActive() {
        return this._magView != null;
    }

    /**
     * setMagFactor:
     * @param {number} 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.
     * @param {number} yMagFactor: The power to set the vertical
     *     magnification factor to of the magnified view.
     */
    setMagFactor(xMagFactor, yMagFactor) {
        this._changeROI({
            xMagFactor,
            yMagFactor,
            redoCursorTracking: this._followingCursor,
            animate: true,
        });
    }

    /**
     * getMagFactor:
     * @returns {number[]} 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
     * @param {GDesktopEnums.MagnifierMouseTrackingMode} mode: the new mode
     */
    setMouseTrackingMode(mode) {
        if (mode >= GDesktopEnums.MagnifierMouseTrackingMode.NONE &&
            mode <= GDesktopEnums.MagnifierMouseTrackingMode.PUSH)
            this._mouseTrackingMode = mode;
    }

    /**
     * getMouseTrackingMode
     * @returns {GDesktopEnums.MagnifierMouseTrackingMode} the current mode
     */
    getMouseTrackingMode() {
        return this._mouseTrackingMode;
    }

    /**
     * setFocusTrackingMode
     * @param {GDesktopEnums.MagnifierFocusTrackingMode} mode: the new mode
     */
    setFocusTrackingMode(mode) {
        this._focusTrackingMode = mode;
        this._syncFocusTracking();
    }

    /**
     * setCaretTrackingMode
     * @param {GDesktopEnums.MagnifierCaretTrackingMode} mode: the new mode
     */
    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.
     * @param {{x: number, y: number, width: number, height: number}} viewPort:
     *     Object defining the position and size of the view port.
     *     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.
     * @param {{x: number, y: number, width: number, height: number}} roi:
     *     Object that defines the region of the screen to magnify.
     *     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.
     * @returns {number[]} 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.
     * @param {bool} lensMode: Whether lensMode should be active
     */
    setLensMode(lensMode) {
        this._lensMode = lensMode;
        if (!this._lensMode)
            this.setScreenPosition(this._screenPosition);
    }

    /**
     * isLensMode:
     * Is lens mode on or off?
     * @returns {bool} The lens mode state.
     */
    isLensMode() {
        return this._lensMode;
    }

    /**
     * setClampScrollingAtEdges:
     * Stop vs. allow scrolling of the magnified contents when it scroll beyond
     * the edges of the screen.
     * @param {bool} 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.
     * @param {GDesktopEnums.MagnifierScreenPosition} inPosition: the position
     */
    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.
     * @returns {GDesktopEnums.MagnifierScreenPosition}: the current position.
     */
    getScreenPosition() {
        return this._screenPosition;
    }

    _clearScrollContentsTimer() {
        if (this._scrollContentsTimerId !== 0) {
            GLib.source_remove(this._scrollContentsTimerId);
            this._scrollContentsTimerId = 0;
        }
    }

    /**
     * scrollToMousePos:
     * Set the region of interest based on the position of the system pointer.
     * @returns {bool}: 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();

        this._clearScrollContentsTimer();
        this._scrollContentsTimerId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, POINTER_REST_TIME, () => {
            this._followingCursor = false;
            if (this._xDelayed !== null && this._yDelayed !== null) {
                this._scrollContentsToDelayed(this._xDelayed, this._yDelayed);
                this._xDelayed = null;
                this._yDelayed = null;
            }

            this._scrollContentsTimerId = 0;

            return GLib.SOURCE_REMOVE;
        });

        // Determine whether the system mouse pointer is over this zoom region.
        return this._isMouseOverRegion();
    }

    _scrollContentsToDelayed(x, y) {
        if (this._followingCursor) {
            this._xDelayed = x;
            this._yDelayed = y;
        } else {
            this.scrollContentsTo(x, y);
        }
    }

    /**
     * scrollContentsTo:
     * Shift the contents of the magnified view such it is centered on the given
     * coordinate.
     * @param {number} x: The x-coord of the point to center on.
     * @param {number} y: The y-coord of the point to center on.
     */
    scrollContentsTo(x, y) {
        if (x < 0 || x > global.screen_width ||
            y < 0 || y > global.screen_height)
            return;

        this._clearScrollContentsTimer();

        this._followingCursor = false;
        this._changeROI({
            xCenter: x,
            yCenter: y,
            animate: true,
        });
    }

    /**
     * addCrosshairs:
     * Add crosshairs centered on the magnified mouse.
     * @param {Crosshairs} 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.
     * @param {bool} flag: whether brightness should be inverted
     */
    setInvertLightness(flag) {
        this._invertLightness = flag;
        if (this._magShaderEffects)
            this._magShaderEffects.setInvertLightness(this._invertLightness);
    }

    /**
     * getInvertLightness:
     * Retrieve whether the lightness is inverted.
     * @returns {bool} whether brightness should be inverted
     */
    getInvertLightness() {
        return this._invertLightness;
    }

    /**
     * setColorSaturation:
     * Set the color saturation of the magnified view.
     * @param {number} saturation: 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.
     * @returns {number} the color saturation
     */
    getColorSaturation() {
        return this._colorSaturation;
    }

    /**
     * setBrightness:
     * Alter the brightness of the magnified view.
     * @param {Object} 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.
     *
     *     {number} brightness.r - the red component
     *     {number} brightness.g - the green component
     *     {number} brightness.b - the blue component
     */
    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.
     * @param {Object} 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.
     *
     *     {number} contrast.r - the red component
     *     {number} contrast.g - the green component
     *     {number} contrast.b - the blue component
     */
    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:
     * Retrieve the contrast of the magnified view.
     * @returns {{r: number, g: number, b: number}}: 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' });
        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();
        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(mainGroup);
        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();

        const uiGroupIsOccluded = this.isActive() && this._isFullScreen();
        Main.uiGroup.set_opacity(uiGroupIsOccluded ? 0 : 255);
    }

    _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,
            animate: 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(params.animate);
    }

    _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(animate = false) {
        if (!this.isActive())
            return;

        let [x, y] = this._screenToViewPort(0, 0);
        this._uiGroupClone.ease({
            x: Math.round(x),
            y: Math.round(y),
            scale_x: this._xMagFactor,
            scale_y: this._yMagFactor,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            duration: animate ? 100 : 0,
        });

        let [mouseX, mouseY] = this._getMousePosition();
        this._mouseActor.ease({
            x: mouseX,
            y: mouseY,
            scale_x: this._xMagFactor,
            scale_y: this._yMagFactor,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            duration: animate ? 100 : 0,
        });

        if (this._crossHairsActor) {
            let [crossX, crossY] = this._getCrossHairsPosition();
            this._crossHairsActor.ease({
                x: crossX,
                y: crossY,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                duration: animate ? 100 : 0,
            });
        }
    }

    _updateMousePosition() {
        let [xMagMouse, yMagMouse] = this._getMousePosition();
        this._mouseActor.set_position(xMagMouse, yMagMouse);

        if (this._crossHairsActor) {
            let [crossX, crossY] = this._getCrossHairsPosition();
            this._crossHairsActor.set_position(crossX, crossY);
        }
    }

    _getMousePosition() {
        let [xMagMouse, yMagMouse] = this._screenToViewPort(
            this._magnifier.xMouse, this._magnifier.yMouse);
        return [Math.round(xMagMouse), Math.round(yMagMouse)];
    }

    _getCrossHairsPosition() {
        let [xMagMouse, yMagMouse] = this._getMousePosition();
        let [groupWidth, groupHeight] = this._crossHairsActor.get_size();

        return [xMagMouse - groupWidth / 2, yMagMouse - groupHeight / 2];
    }

    _monitorsChanged() {
        this._background.set_size(global.screen_width, global.screen_height);
        this._updateScreenPosition();
    }
};

var Crosshairs = GObject.registerClass(
class Crosshairs extends Clutter.Actor {
    _init() {
        // 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;

        super._init({
            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.add_actor(this._horizLeftHair);
        this.add_actor(this._horizRightHair);
        this.add_actor(this._vertTopHair);
        this.add_actor(this._vertBottomHair);
        this._clipSize = [0, 0];
        this._clones = [];
        this.reCenter();
        this._monitorsChangedId = 0;
    }

    _monitorsChanged() {
        this.set_size(global.screen_width * 3, global.screen_height * 3);
        this.reCenter();
    }

    setEnabled(enabled) {
        if (enabled && this._monitorsChangedId === 0) {
            this._monitorsChangedId = Main.layoutManager.connect(
                'monitors-changed', this._monitorsChanged.bind(this));
        } else if (!enabled && this._monitorsChangedId !== 0) {
            Main.layoutManager.disconnect(this._monitorsChangedId);
            this._monitorsChangedId = 0;
        }
    }

    /**
    * 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.
    * @param {ZoomRegion} zoomRegion: The container to add the crosshairs
    *     group to.
    * @param {Clutter.Actor} magnifiedMouse: The mouse actor for the
    *     zoom region -- used to position the crosshairs and properly
    *     layer them below the mouse.
    * @returns {Clutter.Actor} The crosshairs actor, or its clone.
    */
    addToZoomRegion(zoomRegion, magnifiedMouse) {
        let crosshairsActor = null;
        if (zoomRegion && magnifiedMouse) {
            let container = magnifiedMouse.get_parent();
            if (container) {
                crosshairsActor = this;
                if (this.get_parent() != null) {
                    crosshairsActor = new Clutter.Clone({ source: this });
                    this._clones.push(crosshairsActor);

                    // Clones don't share visibility.
                    this.bind_property('visible', crosshairsActor, 'visible',
                                       GObject.BindingFlags.SYNC_CREATE);
                }

                container.add_actor(crosshairsActor);
                container.set_child_above_sibling(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:
     * @param {Clutter.Actor} 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)
            childActor.get_parent().remove_actor(childActor);
        else
            childActor.destroy();
    }

    /**
     * setColor:
     * Set the color of the crosshairs.
     * @param {Clutter.Color} clutterColor: The 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.
     * @returns {ClutterColor} the crosshairs color
     */
    getColor() {
        return this._horizLeftHair.get_color();
    }

    /**
     * setThickness:
     * Set the width of the vertical and horizontal lines of the crosshairs.
     * @param {number} thickness: the new thickness value
     */
    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.
     * @returns {number} The thickness of the crosshairs.
     */
    getThickness() {
        return this._horizLeftHair.get_height();
    }

    /**
     * setOpacity:
     * Set how opaque the crosshairs are.
     * @param {number} 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.
     * @param {number} 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.
     * @returns {number} 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
     * @param {number[]} 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();
        }
    }

    /**
     * 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.
     * @param {number[]=} clipSize: If present, the clip's [width, height].
     */
    reCenter(clipSize) {
        let [groupWidth, groupHeight] = this.get_size();
        let leftLength = this._horizLeftHair.get_width();
        let topLength = this._vertTopHair.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];

        let left = groupWidth / 2 - clipWidth / 2 - leftLength - thickness / 2;
        let right = groupWidth / 2 + clipWidth / 2 + thickness / 2;
        let top = groupHeight / 2 - clipHeight / 2 - topLength - thickness / 2;
        let bottom = groupHeight / 2 + clipHeight / 2 + 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._colorDesaturation.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.
     * @param {bool} invertFlag: Enabled flag.
     */
    setInvertLightness(invertFlag) {
        this._inverse.set_enabled(invertFlag);
    }

    setColorSaturation(factor) {
        this._colorDesaturation.set_factor(1.0 - factor);
        this._colorDesaturation.set_enabled(factor !== 1.0);
    }

    /**
     * setBrightness:
     * Set the brightness of the magnified view.
     * @param {Object} 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.
     *
     *     {number} brightness.r - the red component
     *     {number} brightness.g - the green component
     *     {number} brightness.b - the blue component
     */
    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.
     * @param {Object} 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.
     *
     *     {number} contrast.r - the red component
     *     {number} contrast.g - the green component
     *     {number} contrast.b - the blue component
     */
    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)bluetooth.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Gio, GLib, GnomeBluetooth, GObject } = 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 = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        this._indicator = this._addIndicator();
        this._indicator.icon_name = 'bluetooth-active-symbolic';
        this._hadSetupDevices = global.settings.get_boolean(HAD_BLUETOOTH_DEVICES_SETUP);

        this._client = new GnomeBluetooth.Client();
        this._client.connect('notify::default-adapter', () => {
            const newAdapter = this._client.default_adapter ?? null;
            this._adapter = newAdapter;

            this._deviceNotifyConnected.clear();
            this._sync();
        });
        this._client.connect('notify::default-adapter-powered', this._sync.bind(this));

        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._queueSync.bind(this));

        this._item = new PopupMenu.PopupSubMenuMenuItem(_("Bluetooth"), true);

        this._toggleItem = new PopupMenu.PopupMenuItem('');
        this._toggleItem.connect('activate', () => {
            if (!this._client.default_adapter_powered) {
                this._proxy.BluetoothAirplaneMode = false;
                this._client.default_adapter_powered = true;
            } else {
                this._proxy.BluetoothAirplaneMode = true;
            }
        });
        this._item.menu.addMenuItem(this._toggleItem);

        this._item.menu.addSettingsAction(_("Bluetooth Settings"), 'gnome-bluetooth-panel.desktop');
        this.menu.addMenuItem(this._item);

        this._syncId = 0;
        this._adapter = null;

        this._deviceNotifyConnected = new Set();

        const deviceStore = this._client.get_devices();
        for (let i = 0; i < deviceStore.get_n_items(); i++)
            this._connectDeviceNotify(deviceStore.get_item(i));

        this._client.connect('device-removed', (c, path) => {
            this._deviceNotifyConnected.delete(path);
            this._queueSync.bind(this);
        });
        this._client.connect('device-added', (c, device) => {
            this._connectDeviceNotify(device);
            this._sync();
        });
        Main.sessionMode.connect('updated', this._sync.bind(this));
        this._sync();
    }

    _syncHadSetupDevices() {
        const { defaultAdapter } = this._client;
        if (!defaultAdapter || !this._adapter)
            return; // ignore changes while powering up/down

        const hadSetupDevices = this._getDeviceInfos().length > 0;

        if (this._hadSetupDevices === hadSetupDevices)
            return;

        this._hadSetupDevices = hadSetupDevices;
        global.settings.set_boolean(
            HAD_BLUETOOTH_DEVICES_SETUP, this._hadSetupDevices);
    }

    _connectDeviceNotify(device) {
        const path = device.get_object_path();

        if (this._deviceNotifyConnected.has(path))
            return;

        device.connect('notify::alias', this._queueSync.bind(this));
        device.connect('notify::paired', this._queueSync.bind(this));
        device.connect('notify::trusted', this._queueSync.bind(this));
        device.connect('notify::connected', this._queueSync.bind(this));

        this._deviceNotifyConnected.add(path);
    }

    _getDeviceInfos() {
        const deviceStore = this._client.get_devices();
        let deviceInfos = [];

        for (let i = 0; i < deviceStore.get_n_items(); i++) {
            const device = deviceStore.get_item(i);

            if (device.paired || device.trusted) {
                deviceInfos.push({
                    connected: device.connected,
                    name: device.alias,
                });
            }
        }

        return deviceInfos;
    }

    _queueSync() {
        if (this._syncId)
            return;
        this._syncId = GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
            this._syncId = 0;
            this._sync();
            return GLib.SOURCE_REMOVE;
        });
    }

    _sync() {
        const devices = this._getDeviceInfos();
        const connectedDevices = devices.filter(dev => dev.connected);
        const nConnectedDevices = connectedDevices.length;

        let sensitive = !Main.sessionMode.isLocked && !Main.sessionMode.isGreeter;

        this.menu.setSensitive(sensitive);
        this._indicator.visible = nConnectedDevices > 0;

        const adapterPowered = this._client.default_adapter_powered;
        this._syncHadSetupDevices();

        // Remember if there were setup devices and show the menu
        // if we've seen setup devices and we're not hard blocked
        if (this._hadSetupDevices)
            this._item.visible = !this._proxy.BluetoothHardwareAirplaneMode;
        else
            this._item.visible = adapterPowered;

        this._item.icon.icon_name = adapterPowered
            ? 'bluetooth-active-symbolic' : 'bluetooth-disabled-symbolic';

        if (nConnectedDevices > 1)
            /* 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 = connectedDevices[0].name;
        else if (adapterPowered)
            this._item.label.text = _('Bluetooth On');
        else
            this._item.label.text = _('Bluetooth Off');

        this._toggleItem.label.text = adapterPowered ? _('Turn Off') : _('Turn On');
    }
});
(uuay)__init__.js+/* exported ComponentManager */
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.includes(name))
            .forEach(name => this._enableComponent(name));

        this._enabledComponents
            .filter(name => !newEnabledComponents.includes(name))
            .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)windowPreview.jsY// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WindowPreview */

const {
    Atk, Clutter, GLib, GObject, Graphene, Meta, Pango, Shell, St,
} = imports.gi;

const DND = imports.ui.dnd;
const OverviewControls = imports.ui.overviewControls;

var WINDOW_DND_SIZE = 256;

var WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT = 750;
var WINDOW_OVERLAY_FADE_TIME = 200;

var WINDOW_SCALE_TIME = 200;
var WINDOW_ACTIVE_SIZE_INC = 5; // in each direction

var DRAGGING_WINDOW_OPACITY = 100;

const ICON_SIZE = 64;
const ICON_OVERLAP = 0.7;

const ICON_TITLE_SPACING = 6;

var WindowPreview = GObject.registerClass({
    Properties: {
        'overlay-enabled': GObject.ParamSpec.boolean(
            'overlay-enabled', 'overlay-enabled', 'overlay-enabled',
            GObject.ParamFlags.READWRITE,
            true),
    },
    Signals: {
        'drag-begin': {},
        'drag-cancelled': {},
        'drag-end': {},
        'selected': { param_types: [GObject.TYPE_UINT] },
        'show-chrome': {},
        'size-changed': {},
    },
}, class WindowPreview extends Shell.WindowPreview {
    _init(metaWindow, workspace, overviewAdjustment) {
        this.metaWindow = metaWindow;
        this.metaWindow._delegate = this;
        this._windowActor = metaWindow.get_compositor_private();
        this._workspace = workspace;
        this._overviewAdjustment = overviewAdjustment;

        super._init({
            reactive: true,
            can_focus: true,
            accessible_role: Atk.Role.PUSH_BUTTON,
            offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY,
        });

        const windowContainer = new Clutter.Actor({
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
        });
        this.window_container = windowContainer;

        windowContainer.connect('notify::scale-x',
            () => this._adjustOverlayOffsets());
        // gjs currently can't handle setting an actors layout manager during
        // the initialization of the actor if that layout manager keeps track
        // of its container, so set the layout manager after creating the
        // container
        windowContainer.layout_manager = new Shell.WindowPreviewLayout();
        this.add_child(windowContainer);

        this._addWindow(metaWindow);

        this._delegate = this;

        this._stackAbove = null;

        this._cachedBoundingBox = {
            x: windowContainer.layout_manager.bounding_box.x1,
            y: windowContainer.layout_manager.bounding_box.y1,
            width: windowContainer.layout_manager.bounding_box.get_width(),
            height: windowContainer.layout_manager.bounding_box.get_height(),
        };

        windowContainer.layout_manager.connect(
            'notify::bounding-box', layout => {
                this._cachedBoundingBox = {
                    x: layout.bounding_box.x1,
                    y: layout.bounding_box.y1,
                    width: layout.bounding_box.get_width(),
                    height: layout.bounding_box.get_height(),
                };

                // A bounding box of 0x0 means all windows were removed
                if (layout.bounding_box.get_area() > 0)
                    this.emit('size-changed');
            });

        this._windowActor.connectObject('destroy', () => this.destroy(), this);

        this._updateAttachedDialogs();

        let clickAction = new Clutter.ClickAction();
        clickAction.connect('clicked', () => this._activate());
        clickAction.connect('long-press', this._onLongPress.bind(this));
        this.add_action(clickAction);
        this.connect('destroy', this._onDestroy.bind(this));

        this._draggable = DND.makeDraggable(this, {
            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._overlayEnabled = true;
        this._overlayShown = false;
        this._closeRequested = false;
        this._idleHideOverlayId = 0;

        const tracker = Shell.WindowTracker.get_default();
        const app = tracker.get_window_app(this.metaWindow);
        this._icon = app.create_icon_texture(ICON_SIZE);
        this._icon.add_style_class_name('icon-dropshadow');
        this._icon.set({
            reactive: true,
            pivot_point: new Graphene.Point({ x: 0.5, y: 0.5 }),
        });
        this._icon.add_constraint(new Clutter.BindConstraint({
            source: windowContainer,
            coordinate: Clutter.BindCoordinate.POSITION,
        }));
        this._icon.add_constraint(new Clutter.AlignConstraint({
            source: windowContainer,
            align_axis: Clutter.AlignAxis.X_AXIS,
            factor: 0.5,
        }));
        this._icon.add_constraint(new Clutter.AlignConstraint({
            source: windowContainer,
            align_axis: Clutter.AlignAxis.Y_AXIS,
            pivot_point: new Graphene.Point({ x: -1, y: ICON_OVERLAP }),
            factor: 1,
        }));

        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        this._title = new St.Label({
            visible: false,
            style_class: 'window-caption',
            text: this._getCaption(),
            reactive: true,
        });
        this._title.clutter_text.single_line_mode = true;
        this._title.add_constraint(new Clutter.BindConstraint({
            source: windowContainer,
            coordinate: Clutter.BindCoordinate.X,
        }));
        const iconBottomOverlap = ICON_SIZE * (1 - ICON_OVERLAP);
        this._title.add_constraint(new Clutter.BindConstraint({
            source: windowContainer,
            coordinate: Clutter.BindCoordinate.Y,
            offset: scaleFactor * (iconBottomOverlap + ICON_TITLE_SPACING),
        }));
        this._title.add_constraint(new Clutter.AlignConstraint({
            source: windowContainer,
            align_axis: Clutter.AlignAxis.X_AXIS,
            factor: 0.5,
        }));
        this._title.add_constraint(new Clutter.AlignConstraint({
            source: windowContainer,
            align_axis: Clutter.AlignAxis.Y_AXIS,
            pivot_point: new Graphene.Point({ x: -1, y: 0 }),
            factor: 1,
        }));
        this._title.clutter_text.ellipsize = Pango.EllipsizeMode.END;
        this.label_actor = this._title;
        this.metaWindow.connectObject(
            'notify::title', () => (this._title.text = this._getCaption()),
            this);

        const layout = Meta.prefs_get_button_layout();
        this._closeButtonSide =
            layout.left_buttons.includes(Meta.ButtonFunction.CLOSE)
                ? St.Side.LEFT : St.Side.RIGHT;

        this._closeButton = new St.Button({
            visible: false,
            style_class: 'window-close',
            child: new St.Icon({ icon_name: 'preview-close-symbolic' }),
        });
        this._closeButton.add_constraint(new Clutter.BindConstraint({
            source: windowContainer,
            coordinate: Clutter.BindCoordinate.POSITION,
        }));
        this._closeButton.add_constraint(new Clutter.AlignConstraint({
            source: windowContainer,
            align_axis: Clutter.AlignAxis.X_AXIS,
            pivot_point: new Graphene.Point({ x: 0.5, y: -1 }),
            factor: this._closeButtonSide === St.Side.LEFT ? 0 : 1,
        }));
        this._closeButton.add_constraint(new Clutter.AlignConstraint({
            source: windowContainer,
            align_axis: Clutter.AlignAxis.Y_AXIS,
            pivot_point: new Graphene.Point({ x: -1, y: 0.5 }),
            factor: 0,
        }));
        this._closeButton.connect('clicked', () => this._deleteAll());

        this.add_child(this._title);
        this.add_child(this._icon);
        this.add_child(this._closeButton);

        this._overviewAdjustment.connectObject(
            'notify::value', () => this._updateIconScale(), this);
        this._updateIconScale();

        this.connect('notify::realized', () => {
            if (!this.realized)
                return;

            this._title.ensure_style();
            this._icon.ensure_style();
        });
    }

    _updateIconScale() {
        const { ControlsState } = OverviewControls;
        const { currentState, initialState, finalState } =
            this._overviewAdjustment.getStateTransitionParams();
        const visible =
            initialState === ControlsState.WINDOW_PICKER ||
            finalState === ControlsState.WINDOW_PICKER;
        const scale = visible
            ? 1 - Math.abs(ControlsState.WINDOW_PICKER - currentState) : 0;

        this._icon.set({
            scale_x: scale,
            scale_y: scale,
        });
    }

    _windowCanClose() {
        return this.metaWindow.can_close() &&
               !this._hasAttachedDialogs();
    }

    _getCaption() {
        if (this.metaWindow.title)
            return this.metaWindow.title;

        let tracker = Shell.WindowTracker.get_default();
        let app = tracker.get_window_app(this.metaWindow);
        return app.get_name();
    }

    overlapHeights() {
        const [, titleHeight] = this._title.get_preferred_height(-1);

        const topOverlap = 0;
        const bottomOverlap = ICON_TITLE_SPACING + titleHeight;

        return [topOverlap, bottomOverlap];
    }

    chromeHeights() {
        const [, closeButtonHeight] = this._closeButton.get_preferred_height(-1);
        const [, iconHeight] = this._icon.get_preferred_height(-1);
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * scaleFactor;

        const topOversize = closeButtonHeight / 2;
        const bottomOversize = (1 - ICON_OVERLAP) * iconHeight;

        return [
            topOversize + activeExtraSize,
            bottomOversize + activeExtraSize,
        ];
    }

    chromeWidths() {
        const [, closeButtonWidth] = this._closeButton.get_preferred_width(-1);
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * scaleFactor;

        const leftOversize = this._closeButtonSide === St.Side.LEFT
            ? closeButtonWidth / 2
            : 0;
        const rightOversize = this._closeButtonSide === St.Side.LEFT
            ? 0
            : closeButtonWidth / 2;

        return [
            leftOversize + activeExtraSize,
            rightOversize  + activeExtraSize,
        ];
    }

    showOverlay(animate) {
        if (!this._overlayEnabled)
            return;

        if (this._overlayShown)
            return;

        this._overlayShown = true;
        this._restack();

        // If we're supposed to animate and an animation in our direction
        // is already happening, let that one continue
        const ongoingTransition = this._title.get_transition('opacity');
        if (animate &&
            ongoingTransition &&
            ongoingTransition.get_interval().peek_final_value() === 255)
            return;

        const toShow = this._windowCanClose()
            ? [this._title, this._closeButton]
            : [this._title];

        toShow.forEach(a => {
            a.opacity = 0;
            a.show();
            a.ease({
                opacity: 255,
                duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        });

        const [width, height] = this.window_container.get_size();
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
        const activeExtraSize = WINDOW_ACTIVE_SIZE_INC * 2 * scaleFactor;
        const origSize = Math.max(width, height);
        const scale = (origSize + activeExtraSize) / origSize;

        this.window_container.ease({
            scale_x: scale,
            scale_y: scale,
            duration: animate ? WINDOW_SCALE_TIME : 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        this.emit('show-chrome');
    }

    hideOverlay(animate) {
        if (!this._overlayShown)
            return;

        this._overlayShown = false;
        this._restack();

        // If we're supposed to animate and an animation in our direction
        // is already happening, let that one continue
        const ongoingTransition = this._title.get_transition('opacity');
        if (animate &&
            ongoingTransition &&
            ongoingTransition.get_interval().peek_final_value() === 0)
            return;

        [this._title, this._closeButton].forEach(a => {
            a.opacity = 255;
            a.ease({
                opacity: 0,
                duration: animate ? WINDOW_OVERLAY_FADE_TIME : 0,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => a.hide(),
            });
        });

        this.window_container.ease({
            scale_x: 1,
            scale_y: 1,
            duration: animate ? WINDOW_SCALE_TIME : 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    _adjustOverlayOffsets() {
        // Assume that scale-x and scale-y update always set
        // in lock-step; that allows us to not use separate
        // handlers for horizontal and vertical offsets
        const previewScale = this.window_container.scale_x;
        const [previewWidth, previewHeight] =
            this.window_container.allocation.get_size();

        const heightIncrease =
            Math.floor(previewHeight * (previewScale - 1) / 2);
        const widthIncrease =
            Math.floor(previewWidth * (previewScale - 1) / 2);

        const closeAlign = this._closeButtonSide === St.Side.LEFT ? -1 : 1;

        this._icon.translation_y = heightIncrease;
        this._title.translation_y = heightIncrease;
        this._closeButton.set({
            translation_x: closeAlign * widthIncrease,
            translation_y: -heightIncrease,
        });
    }

    _addWindow(metaWindow) {
        const clone = this.window_container.layout_manager.add_window(metaWindow);
        if (!clone)
            return;

        // We expect this to be used for all interaction rather than
        // the ClutterClone; 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(clone, true);
    }

    vfunc_has_overlaps() {
        return this._hasAttachedDialogs() ||
            this._icon.visible ||
            this._closeButton.visible;
    }

    _deleteAll() {
        const windows = this.window_container.layout_manager.get_windows();

        // Delete all windows, starting from the bottom-most (most-modal) one
        for (const window of windows.reverse())
            window.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._addWindow(win);

        // 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.window_container.layout_manager.get_windows().length > 1;
    }

    _updateAttachedDialogs() {
        let iter = win => {
            let actor = win.get_compositor_private();

            if (!actor)
                return false;
            if (!win.is_attached_dialog())
                return false;

            this._addWindow(win);
            win.foreach_transient(iter);
            return true;
        };
        this.metaWindow.foreach_transient(iter);
    }

    get boundingBox() {
        return { ...this._cachedBoundingBox };
    }

    get windowCenter() {
        return {
            x: this._cachedBoundingBox.x + this._cachedBoundingBox.width / 2,
            y: this._cachedBoundingBox.y + this._cachedBoundingBox.height / 2,
        };
    }

    get overlayEnabled() {
        return this._overlayEnabled;
    }

    set overlayEnabled(enabled) {
        if (this._overlayEnabled === enabled)
            return;

        this._overlayEnabled = enabled;
        this.notify('overlay-enabled');

        if (!enabled)
            this.hideOverlay(false);
        else if (this['has-pointer'] || global.stage.key_focus === this)
            this.showOverlay(true);
    }

    // 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 parent = this.get_parent();
        let actualAbove = this._getActualStackAbove();
        if (actualAbove == null)
            parent.set_child_below_sibling(this, null);
        else
            parent.set_child_above_sibling(this, actualAbove);
    }

    _onDestroy() {
        this.metaWindow._delegate = null;
        this._delegate = null;

        if (this._longPressLater) {
            Meta.later_remove(this._longPressLater);
            delete this._longPressLater;
        }

        if (this._idleHideOverlayId > 0) {
            GLib.source_remove(this._idleHideOverlayId);
            this._idleHideOverlayId = 0;
        }

        if (this.inDrag) {
            this.emit('drag-end');
            this.inDrag = false;
        }
    }

    _activate() {
        this._selected = true;
        this.emit('selected', global.get_current_time());
    }

    vfunc_enter_event(crossingEvent) {
        this.showOverlay(true);
        return super.vfunc_enter_event(crossingEvent);
    }

    vfunc_leave_event(crossingEvent) {
        if ((crossingEvent.flags & Clutter.EventFlags.FLAG_GRAB_NOTIFY) !== 0 &&
            global.stage.get_grab_actor() === this._closeButton)
            return super.vfunc_leave_event(crossingEvent);

        if (this._idleHideOverlayId > 0)
            GLib.source_remove(this._idleHideOverlayId);

        this._idleHideOverlayId = GLib.timeout_add(
            GLib.PRIORITY_DEFAULT,
            WINDOW_OVERLAY_IDLE_HIDE_TIMEOUT, () => {
                if (this._closeButton['has-pointer'] ||
                    this._title['has-pointer'])
                    return GLib.SOURCE_CONTINUE;

                if (!this['has-pointer'])
                    this.hideOverlay(true);

                this._idleHideOverlayId = 0;
                return GLib.SOURCE_REMOVE;
            });

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

        return super.vfunc_leave_event(crossingEvent);
    }

    vfunc_key_focus_in() {
        super.vfunc_key_focus_in();
        this.showOverlay(true);
    }

    vfunc_key_focus_out() {
        super.vfunc_key_focus_out();

        if (global.stage.get_grab_actor() !== this._closeButton)
            this.hideOverlay(true);
    }

    vfunc_key_press_event(keyEvent) {
        let symbol = keyEvent.keyval;
        let isEnter = symbol == Clutter.KEY_Return || symbol == Clutter.KEY_KP_Enter;
        if (isEnter) {
            this._activate();
            return true;
        }

        return super.vfunc_key_press_event(keyEvent);
    }

    _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();

            if (this._longPressLater)
                return true;

            // A click cancels a long-press before any click handler is
            // run - make sure to not start a drag in that case
            this._longPressLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                delete this._longPressLater;
                if (this._selected) {
                    this._selected = false;
                    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.showOverlay(true);
        }
        return true;
    }

    _restack() {
        // 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.
        const parent = this.get_parent();
        if (parent !== null) {
            if (this._overlayShown)
                parent.set_child_above_sibling(this, null);
            else if (this._stackAbove === null)
                parent.set_child_below_sibling(this, null);
            else if (!this._stackAbove._overlayShown)
                parent.set_child_above_sibling(this, this._stackAbove);
        }
    }

    _onDragBegin(_draggable, _time) {
        this.inDrag = true;
        this.hideOverlay(false);
        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) {
        return this._workspace.acceptDrop(source, actor, x, y, time);
    }

    _onDragCancelled(_draggable, _time) {
        this.emit('drag-cancelled');
    }

    _onDragEnd(_draggable, _time, _snapback) {
        this.inDrag = false;

        this._restack();

        if (this['has-pointer'])
            this.showOverlay(true);

        this.emit('drag-end');
    }
});
(uuay)main.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported componentManager, notificationDaemon, windowAttentionHandler,
            ctrlAltTabManager, padOsdService, osdWindowManager,
            osdMonitorLabeler, shellMountOpDBusService, shellDBusService,
            shellAccessDialogDBusService, shellAudioSelectionDBusService,
            screenSaverDBus, uiGroup, magnifier, xdndHandler, keyboard,
            kbdA11yDialog, introspectService, start, pushModal, popModal,
            activateWindow, moveWindowToMonitorAndWorkspace,
            createLookingGlass, initializeDeferredWork,
            getThemeStylesheet, setThemeStylesheet, screenshotUI */

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

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 WelcomeDialog = imports.ui.welcomeDialog;
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 Screenshot = imports.ui.screenshot;
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 LocatePointer = imports.ui.locatePointer;
const PointerA11yTimeout = imports.ui.pointerA11yTimeout;
const ParentalControlsManager = imports.misc.parentalControlsManager;
const Config = imports.misc.config;
const Util = imports.misc.util;

const WELCOME_DIALOG_LAST_SHOWN_VERSION = 'welcome-dialog-last-shown-version';
// Make sure to mention the point release, otherwise it will show every time
// until this version is current
const WELCOME_DIALOG_LAST_TOUR_CHANGE = '40.beta';
const LOG_DOMAIN = 'GNOME Shell';
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 welcomeDialog = 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 screenshotUI = null;
var shellAccessDialogDBusService = null;
var shellAudioSelectionDBusService = null;
var shellDBusService = null;
var shellMountOpDBusService = null;
var screenSaverDBus = 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;
var locatePointer = null;
let _startDate;
let _defaultCssStylesheet = null;
let _cssStylesheet = null;
let _themeResource = null;
let _oskResource = null;
let _iconResource = null;

Gio._promisify(Gio.File.prototype, 'delete_async');
Gio._promisify(Gio.File.prototype, 'touch_async');

let _remoteAccessInhibited = false;

function _sessionUpdated() {
    if (sessionMode.isPrimary)
        _loadDefaultStylesheet();

    wm.allowKeybinding('overlay-key', Shell.ActionMode.NORMAL |
                                      Shell.ActionMode.OVERVIEW);

    wm.allowKeybinding('locate-pointer-key', Shell.ActionMode.ALL);

    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();
        if (welcomeDialog)
            welcomeDialog.close();
    }

    let remoteAccessController = global.backend.get_remote_access_controller();
    if (remoteAccessController && !global.backend.is_headless()) {
        if (sessionMode.allowScreencast && _remoteAccessInhibited) {
            remoteAccessController.uninhibit_remote_access();
            _remoteAccessInhibited = false;
        } else if (!sessionMode.allowScreencast && !_remoteAccessInhibited) {
            remoteAccessController.inhibit_remote_access();
            _remoteAccessInhibited = true;
        }
    }

    if (!GLib.getenv("SHELL_DEBUG"))
        global.set_debug_flags(sessionMode.debugFlags.join(':'));
}

function start() {
    // These are here so we don't break compatibility.
    global.logError = globalThis.log;
    global.log = globalThis.log;

    // Chain up async errors reported from C
    global.connect('notify-error', (global, msg, detail) => {
        notifyError(msg, detail);
    });

    let currentDesktop = GLib.getenv('XDG_CURRENT_DESKTOP');
    if (!currentDesktop || !currentDesktop.split(':').includes('GNOME'))
        Gio.DesktopAppInfo.set_desktop_env('GNOME');

    sessionMode = new SessionMode.SessionMode();
    sessionMode.connect('updated', _sessionUpdated);

    St.Settings.get().connect('notify::high-contrast', _loadDefaultStylesheet);
    St.Settings.get().connect('notify::gtk-theme', _loadDefaultStylesheet);
    St.Settings.get().connect('notify::gtk-theme-variant', _loadDefaultStylesheet);

    ubuntuSettings = new Gio.Settings({ schema_id: 'org.gnome.shell.ubuntu' });
    ubuntuSettings.connect('changed::color-scheme', _loadDefaultStylesheet);
    St.Settings.get()._ubuntuSettings = ubuntuSettings;

    // Initialize ParentalControlsManager before the UI
    ParentalControlsManager.getDefault();

    _initializeUI();

    shellAccessDialogDBusService = new AccessDialog.AccessDialogDBus();
    shellAudioSelectionDBusService = new AudioDeviceSelection.AudioDeviceSelectionDBus();
    shellDBusService = new ShellDBus.GnomeShell();
    shellMountOpDBusService = new ShellMountOperation.GnomeShellMountOpHandler();

    const watchId = Gio.DBus.session.watch_name('org.gnome.Shell.Notifications',
        Gio.BusNameWatcherFlags.AUTO_START,
        bus => bus.unwatch_name(watchId),
        bus => bus.unwatch_name(watchId));

    _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();
    _loadIcons();
    _loadOskLayouts();
    _loadDefaultStylesheet();

    new AnimationsSettings();

    // Setup the stage hierarchy early
    layoutManager = new Layout.LayoutManager();

    // Various parts of the codebase still refer to Main.uiGroup
    // instead of using the layoutManager. This keeps that code
    // working until it's updated.
    uiGroup = layoutManager.uiGroup;

    padOsdService = new PadOsd.PadOsdService();
    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();
    locatePointer = new LocatePointer.LocatePointer();

    if (LoginManager.canLock())
        screenShield = new ScreenShield.ScreenShield();

    inputMethod = new InputMethod.InputMethod();
    Clutter.get_default_backend().set_input_method(inputMethod);

    screenshotUI = new Screenshot.ScreenshotUI();

    messageTray = new MessageTray.MessageTray();
    panel = new Panel.Panel();
    keyboard = new Keyboard.KeyboardManager();
    notificationDaemon = new NotificationDaemon.NotificationDaemon();
    windowAttentionHandler = new WindowAttentionHandler.WindowAttentionHandler();
    componentManager = new Components.ComponentManager();

    introspectService = new Introspect.IntrospectService();

    layoutManager.init();
    overview.init();

    new PointerA11yTimeout.PointerA11yTimeout();

    global.connect('locate-pointer', () => {
        locatePointer.show();
    });

    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', loadTheme);

    global.context.connect('notify::unsafe-mode', () => {
        if (!global.context.unsafe_mode)
            return; // we're safe
        if (lookingGlass?.isOpen)
            return; // assume user action

        const source = new MessageTray.SystemNotificationSource();
        messageTray.add(source);
        const notification = new MessageTray.Notification(source,
            _('System was put in unsafe mode'),
            _('Applications now have unrestricted access'));
        notification.addAction(_('Undo'),
            () => (global.context.unsafe_mode = false));
        notification.setTransient(true);
        source.showNotification(notification);
    });

    // 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
    GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
        Shell.util_sd_notify();
        global.context.notify_ready();
        return GLib.SOURCE_REMOVE;
    });

    _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') {
            GLib.log_structured(LOG_DOMAIN, GLib.LogLevelFlags.LEVEL_MESSAGE, {
                'MESSAGE': `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.'));
        } else if (sessionMode.showWelcomeDialog) {
            _handleShowWelcomeScreen();
        }

        if (sessionMode.currentMode !== 'gdm' &&
            sessionMode.currentMode !== 'initial-setup')
            _handleLockScreenWarning();

        LoginManager.registerSessionWithGDM();

        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);
        }
    });
}

function _handleShowWelcomeScreen() {
    const lastShownVersion = global.settings.get_string(WELCOME_DIALOG_LAST_SHOWN_VERSION);
    if (Util.GNOMEversionCompare(WELCOME_DIALOG_LAST_TOUR_CHANGE, lastShownVersion) > 0) {
        openWelcomeDialog();
        global.settings.set_string(WELCOME_DIALOG_LAST_SHOWN_VERSION, Config.PACKAGE_VERSION);
    }
}

async function _handleLockScreenWarning() {
    const path = `${global.userdatadir}/lock-warning-shown`;
    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 _realpath(path) {
    try {
        while (GLib.file_test(path, GLib.FileTest.IS_SYMLINK))
            path = GLib.file_read_link(path);
    } catch (e) { }
    return path;
}

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]);
        stylesheet = Gio.file_new_for_path(_realpath(path));
        if (stylesheet.query_exists(null))
            return stylesheet;
    }

    stylesheet = Gio.File.new_for_path(_realpath(`${global.datadir}/theme/${name}`));
    if (stylesheet.query_exists(null))
        return stylesheet;

    return null;
}

function _getYaruStyleSheet(themeVariant) {
    const colorScheme = St.Settings.get()._ubuntuSettings.get_string('color-scheme');
    const baseThemeName = sessionMode.stylesheetName.split(".css").at(0);
    const isDark = themeVariant === 'dark' || themeVariant?.endsWith('-dark');
    let colorSchemeVariant;

    if (isDark && colorScheme == 'prefer-light') {
        colorSchemeVariant = themeVariant.split('-').slice(0, -1).join('-');
    } else if (!isDark && colorScheme == 'prefer-dark' ) {
        colorSchemeVariant = themeVariant ? `${themeVariant}-dark` : 'dark';
    }

    if (colorSchemeVariant !== undefined) {
        if (colorSchemeVariant.length)
            colorSchemeVariant = `-${colorSchemeVariant}`;
        const stylesheet = _getStylesheet(`${baseThemeName}${colorSchemeVariant}.css`);
        if (stylesheet)
            return stylesheet;
    }

    if (!themeVariant)
        return null;

    const stylesheet = _getStylesheet(`${baseThemeName}-${themeVariant}.css`);

    // Try to use the dark theme if a dark variant is selected
    if (!stylesheet && isDark)
        return _getStylesheet(`${baseThemeName}-dark.css`);

    return stylesheet;
}

function _getDefaultStylesheet() {
    let stylesheet = null;
    let name = sessionMode.stylesheetName;

    // Look for a high-contrast variant first
    if (St.Settings.get().high_contrast)
        stylesheet = _getStylesheet(name.replace('.css', '-high-contrast.css'));

    if (stylesheet == null) {
        const settings = St.Settings.get();

        if (settings.gtk_theme == 'Yaru')
            stylesheet = _getYaruStyleSheet(settings.gtk_theme_variant?.toLowerCase());
    }

    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 {?Gio.File}: A #GFile that contains the theme CSS,
 *          null if using the default
 */
function getThemeStylesheet() {
    return _cssStylesheet;
}

/**
 * setThemeStylesheet:
 * @param {string=} 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}/${sessionMode.themeResourceName}`);
    _themeResource._register();
}

/** @private */
function _loadIcons() {
    _iconResource = Gio.Resource.load(`${global.datadir}/${sessionMode.iconsResourceName}`);
    _iconResource._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 '${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:
 * @param {string} msg: A message
 * @param {string} 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.showNotification(notification);
}

/**
 * notifyError:
 * @param {string} msg: An error message
 * @param {string} 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);
}

/**
 * _findModal:
 *
 * @param {Clutter.Grab} grab - grab
 *
 * Private function.
 *
 */
function _findModal(grab) {
    for (let i = 0; i < modalActorFocusStack.length; i++) {
        if (modalActorFocusStack[i].grab === grab)
            return i;
    }
    return -1;
}

/**
 * pushModal:
 * @param {Clutter.Actor} actor: actor which will be given keyboard focus
 * @param {Object=} 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 {Clutter.Grab}: the grab handle created
 */
function pushModal(actor, params) {
    params = Params.parse(params, {
        timestamp: global.get_current_time(),
        options: 0,
        actionMode: Shell.ActionMode.NONE,
    });

    let grab = global.stage.grab(actor);

    if (modalCount === 0)
        Meta.disable_unredirect_for_display(global.display);

    modalCount += 1;
    let actorDestroyId = actor.connect('destroy', () => {
        let index = _findModal(grab);
        if (index >= 0)
            popModal(grab);
    });

    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,
        grab,
        destroyId: actorDestroyId,
        prevFocus,
        prevFocusDestroyId,
        actionMode,
    });

    actionMode = params.actionMode;
    global.stage.set_key_focus(actor);
    return grab;
}

/**
 * popModal:
 * @param {Clutter.Grab} grab - the grab given by pushModal()
 * @param {number=} 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(grab, timestamp) {
    if (timestamp == undefined)
        timestamp = global.get_current_time();

    let focusIndex = _findModal(grab);
    if (focusIndex < 0) {
        global.stage.set_key_focus(null);
        actionMode = Shell.ActionMode.NORMAL;

        throw new Error('incorrect pop');
    }

    modalCount -= 1;

    let record = modalActorFocusStack[focusIndex];
    record.actor.disconnect(record.destroyId);

    record.grab.dismiss();

    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();
    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();
}

function openWelcomeDialog() {
    if (welcomeDialog === null)
        welcomeDialog = new WelcomeDialog.WelcomeDialog();

    welcomeDialog.open();
}

/**
 * activateWindow:
 * @param {Meta.Window} window: the window to activate
 * @param {number=} time: current event time
 * @param {number=} workspaceNum:  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();
}

/**
 * Move @window to the specified monitor and workspace.
 *
 * @param {Meta.Window} window - the window to move
 * @param {number} monitorIndex - the requested monitor
 * @param {number} workspaceIndex - the requested workspace
 * @param {bool} append - create workspace if it doesn't exist
 */
function moveWindowToMonitorAndWorkspace(window, monitorIndex, workspaceIndex, append = false) {
    // 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 (window.get_monitor() !== monitorIndex) {
        // Wait for the monitor change to take effect
        const id = global.display.connect('window-entered-monitor',
            (dsp, num, w) => {
                if (w !== window)
                    return;
                window.change_workspace_by_index(workspaceIndex, append);
                global.display.disconnect(id);
            });
        window.move_to_monitor(monitorIndex);
    } else {
        window.change_workspace_by_index(workspaceIndex, append);
    }
}

// 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) {
        GLib.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:
 * @param {Clutter.Actor} actor: an actor
 * @param {callback} 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 {string}: A string work identifier
 */
function initializeDeferredWork(actor, callback) {
    // Turn into a string so we can use as an object property
    let workId = `${++_deferredWorkSequence}`;
    _deferredWorkData[workId] = {
        actor,
        callback,
    };
    actor.connect('notify::mapped', () => {
        if (!(actor.mapped && _deferredWorkQueue.includes(workId)))
            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:
 * @param {string} 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 ${workId}`;
        logError(new Error(message), message);
        return;
    }
    if (!_deferredWorkQueue.includes(workId))
        _deferredWorkQueue.push(workId);
    if (data.actor.mapped) {
        _queueBeforeRedraw(workId);
    } else if (_deferredTimeoutId == 0) {
        _deferredTimeoutId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, DEFERRED_TIMEOUT_SECONDS, () => {
            _runAllDeferredWork();
            _deferredTimeoutId = 0;
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(_deferredTimeoutId, '[gnome-shell] _runAllDeferredWork');
    }
}

var RestartMessage = GObject.registerClass(
class RestartMessage extends ModalDialog.ModalDialog {
    _init(message) {
        super._init({
            shellReactive: true,
            styleClass: 'restart-message headline',
            shouldFadeIn: false,
            destroyOnClose: true,
        });

        let label = new St.Label({
            text: message,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this.contentLayout.add_child(label);
        this.buttonLayout.hide();
    }
});

function showRestartMessage(message) {
    let restartMessage = new RestartMessage(message);
    restartMessage.open();
}

var AnimationsSettings = class {
    constructor() {
        let backend = global.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)basic.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported run, finish, script_topBarNavDone, script_notificationShowDone,
   script_notificationCloseDone, script_overviewShowDone,
   script_applicationsShowStart, script_applicationsShowDone, METRICS,
*/
/* eslint camelcase: ["error", { properties: "never", allow: ["^script_"] }] */

const { St } = imports.gi;

const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Scripting = imports.ui.scripting;

// This script tests the most important (basic) functionality of the shell.

var METRICS = {};

async function run() {
    /* eslint-disable no-await-in-loop */
    Scripting.defineScriptEvent('topBarNavStart', 'Starting to navigate the top bar');
    Scripting.defineScriptEvent('topBarNavDone', 'Done navigating the top bar');
    Scripting.defineScriptEvent('notificationShowStart', 'Showing a notification');
    Scripting.defineScriptEvent('notificationShowDone', 'Done showing a notification');
    Scripting.defineScriptEvent('notificationCloseStart', 'Closing a notification');
    Scripting.defineScriptEvent('notificationCloseDone', 'Done closing a notification');
    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');

    Main.overview.connect('shown',
        () => Scripting.scriptEvent('overviewShowDone'));

    await Scripting.sleep(1000);

    // navigate through top bar
    Scripting.scriptEvent('topBarNavStart');
    Main.panel.statusArea.aggregateMenu.menu.open();
    await Scripting.sleep(400);

    const { menuManager } = Main.panel;
    while (menuManager.activeMenu &&
        Main.panel.navigate_focus(menuManager.activeMenu.sourceActor,
            St.DirectionType.TAB_BACKWARD, false))
        await Scripting.sleep(400);
    Scripting.scriptEvent('topBarNavDone');

    await Scripting.sleep(1000);

    // notification
    const source = new MessageTray.SystemNotificationSource();
    Main.messageTray.add(source);

    Scripting.scriptEvent('notificationShowStart');
    source.connect('notification-show',
        () => Scripting.scriptEvent('notificationShowDone'));

    const notification = new MessageTray.Notification(source,
        'A test notification');
    source.showNotification(notification);
    await Scripting.sleep(400);

    Main.panel.statusArea.dateMenu.menu.open();
    await Scripting.sleep(400);

    Scripting.scriptEvent('notificationCloseStart');
    notification.connect('destroy',
        () => Scripting.scriptEvent('notificationCloseDone'));

    notification.destroy();
    await Scripting.sleep(400);

    Main.panel.statusArea.dateMenu.menu.close();
    await Scripting.waitLeisure();

    await Scripting.sleep(1000);

    // overview (window picker)
    Scripting.scriptEvent('overviewShowStart');
    Main.overview.show();
    await Scripting.waitLeisure();
    Main.overview.hide();
    await Scripting.waitLeisure();

    await Scripting.sleep(1000);

    // overview (app picker)
    Main.overview.show();
    await Scripting.waitLeisure();

    Scripting.scriptEvent('applicationsShowStart');
    // eslint-disable-next-line require-atomic-updates
    Main.overview.dash.showAppsButton.checked = true;
    await Scripting.waitLeisure();
    Scripting.scriptEvent('applicationsShowDone');
    // eslint-disable-next-line require-atomic-updates
    Main.overview.dash.showAppsButton.checked = false;
    await Scripting.waitLeisure();

    Main.overview.hide();
    await Scripting.waitLeisure();
    /* eslint-enable no-await-in-loop */
}

let topBarNav = false;
let notificationShown = false;
let notificationClosed = false;
let windowPickerShown = false;
let appPickerShown = false;

function script_topBarNavDone() {
    topBarNav = true;
}

function script_notificationShowDone() {
    notificationShown = true;
}

function script_notificationCloseDone() {
    notificationClosed = true;
}

function script_overviewShowDone() {
    windowPickerShown = true;
}

function script_applicationsShowDone() {
    appPickerShown = true;
}

function finish() {
    if (!topBarNav)
        throw new Error('Failed to navigate top bar');

    if (!notificationShown)
        throw new Error('Failed to show notification');

    if (!notificationClosed)
        throw new Error('Failed to close notification');

    if (!windowPickerShown)
        throw new Error('Failed to show window picker');

    if (!appPickerShown)
        throw new Error('Failed to show app picker');
}
(uuay)dialog.js"(// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Dialog, MessageDialogContent, ListSection, ListSectionItem */

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

function _setLabel(label, value) {
    label.set({
        text: value || '',
        visible: value !== null,
    });
}

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

        this._initialKeyFocus = null;
        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._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',
            y_expand: true,
        });
        this._dialog.add_child(this.contentLayout);

        this.buttonLayout = new St.Widget({
            layout_manager: new Clutter.BoxLayout({ homogeneous: true }),
        });
        this._dialog.add_child(this.buttonLayout);
    }

    makeInactive() {
        this.buttonLayout.get_children().forEach(c => c.set_reactive(false));
    }

    _onDestroy() {
        this.makeInactive();
    }

    vfunc_event(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) {
        this._initialKeyFocus?.disconnectObject(this);

        this._initialKeyFocus = actor;

        actor.connectObject('destroy',
            () => (this._initialKeyFocus = null), this);
    }

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

    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,
        });
        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: {
        'title': GObject.ParamSpec.string(
            'title', 'title', 'title',
            GObject.ParamFlags.READWRITE |
            GObject.ParamFlags.CONSTRUCT,
            null),
        'description': GObject.ParamSpec.string(
            'description', 'description', 'description',
            GObject.ParamFlags.READWRITE |
            GObject.ParamFlags.CONSTRUCT,
            null),
    },
}, class MessageDialogContent extends St.BoxLayout {
    _init(params) {
        this._title = new St.Label({ style_class: 'message-dialog-title' });
        this._description = new St.Label({ style_class: 'message-dialog-description' });

        this._description.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        this._description.clutter_text.line_wrap = true;

        let defaultParams = {
            style_class: 'message-dialog-content',
            x_expand: true,
            vertical: true,
        };
        super._init(Object.assign(defaultParams, params));

        this.connect('notify::size', this._updateTitleStyle.bind(this));
        this.connect('destroy', this._onDestroy.bind(this));

        this.add_child(this._title);
        this.add_child(this._description);
    }

    _onDestroy() {
        if (this._updateTitleStyleLater) {
            Meta.later_remove(this._updateTitleStyleLater);
            delete this._updateTitleStyleLater;
        }
    }

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

    get description() {
        return this._description.text;
    }

    _updateTitleStyle() {
        if (!this._title.mapped)
            return;

        this._title.ensure_style();
        const [, titleNatWidth] = this._title.get_preferred_width(-1);

        if (titleNatWidth > this.width) {
            if (this._updateTitleStyleLater)
                return;

            this._updateTitleStyleLater = Meta.later_add(Meta.LaterType.BEFORE_REDRAW, () => {
                this._updateTitleStyleLater = 0;
                this._title.add_style_class_name('lightweight');
                return GLib.SOURCE_REMOVE;
            });
        }
    }

    set title(title) {
        if (this._title.text === title)
            return;

        _setLabel(this._title, title);

        this._title.remove_style_class_name('lightweight');
        this._updateTitleStyle();

        this.notify('title');
    }

    set description(description) {
        if (this._description.text === description)
            return;

        _setLabel(this._description, description);
        this.notify('description');
    }
});

var ListSection = GObject.registerClass({
    Properties: {
        'title': GObject.ParamSpec.string(
            'title', 'title', 'title',
            GObject.ParamFlags.READWRITE |
            GObject.ParamFlags.CONSTRUCT,
            null),
    },
}, class ListSection extends St.BoxLayout {
    _init(params) {
        this._title = new St.Label({ style_class: 'dialog-list-title' });

        this._listScrollView = new St.ScrollView({
            style_class: 'dialog-list-scrollview',
            hscrollbar_policy: St.PolicyType.NEVER,
        });

        this.list = new St.BoxLayout({
            style_class: 'dialog-list-box',
            vertical: true,
        });
        this._listScrollView.add_actor(this.list);

        let defaultParams = {
            style_class: 'dialog-list',
            x_expand: true,
            vertical: true,
        };
        super._init(Object.assign(defaultParams, params));

        this.label_actor = this._title;
        this.add_child(this._title);
        this.add_child(this._listScrollView);
    }

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

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

var ListSectionItem = GObject.registerClass({
    Properties: {
        'icon-actor':  GObject.ParamSpec.object(
            'icon-actor', 'icon-actor', 'Icon actor',
            GObject.ParamFlags.READWRITE,
            Clutter.Actor.$gtype),
        'title': GObject.ParamSpec.string(
            'title', 'title', 'title',
            GObject.ParamFlags.READWRITE |
            GObject.ParamFlags.CONSTRUCT,
            null),
        'description': GObject.ParamSpec.string(
            'description', 'description', 'description',
            GObject.ParamFlags.READWRITE |
            GObject.ParamFlags.CONSTRUCT,
            null),
    },
}, class ListSectionItem extends St.BoxLayout {
    _init(params) {
        this._iconActorBin = new St.Bin();

        let textLayout = new St.BoxLayout({
            vertical: true,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this._title = new St.Label({ style_class: 'dialog-list-item-title' });

        this._description = new St.Label({
            style_class: 'dialog-list-item-title-description',
        });

        textLayout.add_child(this._title);
        textLayout.add_child(this._description);

        let defaultParams = { style_class: 'dialog-list-item' };
        super._init(Object.assign(defaultParams, params));

        this.label_actor = this._title;
        this.add_child(this._iconActorBin);
        this.add_child(textLayout);
    }

    get iconActor() {
        return this._iconActorBin.get_child();
    }

    set iconActor(actor) {
        this._iconActorBin.set_child(actor);
        this.notify('icon-actor');
    }

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

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

    get description() {
        return this._description.text;
    }

    set description(description) {
        _setLabel(this._description, description);
        this.notify('description');
    }
});
(uuay)system.js.// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { 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 Indicator = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        this._systemActions = new SystemActions.getDefault();

        this._createSubMenu();

        this._loginScreenItem.connect('notify::visible',
            () => this._updateSessionSubMenu());
        this._logoutItem.connect('notify::visible',
            () => this._updateSessionSubMenu());
        this._suspendItem.connect('notify::visible',
            () => this._updateSessionSubMenu());
        this._powerOffItem.connect('notify::visible',
            () => this._updateSessionSubMenu());
        this._restartItem.connect('notify::visible',
            () => this._updateSessionSubMenu());
        // 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._updateSessionSubMenu();

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

    _sessionUpdated() {
        this._settingsItem.visible = Main.sessionMode.allowSettings;
    }

    _updateSessionSubMenu() {
        this._sessionSubMenu.visible =
            this._loginScreenItem.visible ||
            this._logoutItem.visible ||
            this._suspendItem.visible ||
            this._restartItem.visible ||
            this._powerOffItem.visible;
    }

    _createSubMenu() {
        let bindFlags = GObject.BindingFlags.DEFAULT | GObject.BindingFlags.SYNC_CREATE;
        let item;

        item = new PopupMenu.PopupImageMenuItem(
            this._systemActions.getName('lock-orientation'),
            this._systemActions.orientation_lock_icon);

        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateLockOrientation();
        });
        this.menu.addMenuItem(item);
        this._orientationLockItem = item;
        this._systemActions.bind_property('can-lock-orientation',
            this._orientationLockItem, 'visible',
            bindFlags);
        this._systemActions.connect('notify::orientation-lock-icon', () => {
            let iconName = this._systemActions.orientation_lock_icon;
            let labelText = this._systemActions.getName("lock-orientation");

            this._orientationLockItem.setIcon(iconName);
            this._orientationLockItem.label.text = labelText;
        });

        let app = this._settingsApp = Shell.AppSystem.get_default().lookup_app(
            'gnome-control-center.desktop');
        if (app) {
            const [icon] = app.app_info.get_icon().names;
            const name = app.app_info.get_name();
            item = new PopupMenu.PopupImageMenuItem(name, icon);
            item.connect('activate', () => {
                this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
                Main.overview.hide();
                this._settingsApp.activate();
            });
            this.menu.addMenuItem(item);
            this._settingsItem = item;
        } else {
            log('Missing required core component Settings, expect trouble…');
            this._settingsItem = new St.Widget();
        }

        item = new PopupMenu.PopupImageMenuItem(_('Lock'), 'changes-prevent-symbolic');
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateLockScreen();
        });
        this.menu.addMenuItem(item);
        this._lockScreenItem = item;
        this._systemActions.bind_property('can-lock-screen',
            this._lockScreenItem, 'visible',
            bindFlags);

        this._sessionSubMenu = new PopupMenu.PopupSubMenuMenuItem(
            _('Power Off / Log Out'), true);
        this._sessionSubMenu.icon.icon_name = 'system-shutdown-symbolic';

        item = new PopupMenu.PopupMenuItem(_('Suspend'));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateSuspend();
        });
        this._sessionSubMenu.menu.addMenuItem(item);
        this._suspendItem = item;
        this._systemActions.bind_property('can-suspend',
            this._suspendItem, 'visible',
            bindFlags);

        item = new PopupMenu.PopupMenuItem(_('Restart…'));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateRestart();
        });
        this._sessionSubMenu.menu.addMenuItem(item);
        this._restartItem = item;
        this._systemActions.bind_property('can-restart',
            this._restartItem, 'visible',
            bindFlags);

        item = new PopupMenu.PopupMenuItem(_('Power Off…'));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activatePowerOff();
        });
        this._sessionSubMenu.menu.addMenuItem(item);
        this._powerOffItem = item;
        this._systemActions.bind_property('can-power-off',
            this._powerOffItem, 'visible',
            bindFlags);

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

        item = new PopupMenu.PopupMenuItem(_('Log Out'));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateLogout();
        });
        this._sessionSubMenu.menu.addMenuItem(item);
        this._logoutItem = item;
        this._systemActions.bind_property('can-logout',
            this._logoutItem, 'visible',
            bindFlags);

        item = new PopupMenu.PopupMenuItem(_('Switch User…'));
        item.connect('activate', () => {
            this.menu.itemActivated(BoxPointer.PopupAnimation.NONE);
            this._systemActions.activateSwitchUser();
        });
        this._sessionSubMenu.menu.addMenuItem(item);
        this._loginScreenItem = item;
        this._systemActions.bind_property('can-switch-user',
            this._loginScreenItem, 'visible',
            bindFlags);

        this.menu.addMenuItem(this._sessionSubMenu);
    }
});
(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/>.
 */
/* exported AuthList */

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

const SCROLL_ANIMATION_TIME = 500;

const AuthListItem = GObject.registerClass({
    Signals: { 'activate': {} },
}, class AuthListItem extends St.Button {
    _init(key, text) {
        this.key = key;
        const label = new St.Label({
            text,
            style_class: 'login-dialog-auth-list-label',
            y_align: Clutter.ActorAlign.CENTER,
            x_expand: false,
        });

        super._init({
            style_class: 'login-dialog-auth-list-item',
            button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
            can_focus: true,
            child: label,
            reactive: true,
        });

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

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

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

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

var AuthList = GObject.registerClass({
    Signals: {
        'activate': { param_types: [GObject.TYPE_STRING] },
        'item-added': { param_types: [AuthListItem.$gtype] },
    },
}, class AuthList extends St.BoxLayout {
    _init() {
        super._init({
            vertical: true,
            style_class: 'login-dialog-auth-list-layout',
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this.label = new St.Label({ style_class: 'login-dialog-auth-list-title' });
        this.add_child(this.label);

        this._scrollView = new St.ScrollView({
            style_class: 'login-dialog-auth-list-view',
        });
        this._scrollView.set_policy(
            St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        this.add_child(this._scrollView);

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

        this._scrollView.add_actor(this._box);
        this._items = new Map();

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

    _moveFocusToItems() {
        let hasItems = this.numItems > 0;

        if (!hasItems)
            return;

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

        let focusSet = this.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.key);
    }

    scrollToItem(item) {
        let box = item.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.ease(value, {
            duration: SCROLL_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    addItem(key, text) {
        this.removeItem(key);

        let item = new AuthListItem(key, text);
        this._box.add(item);

        this._items.set(key, item);

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

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

        this._moveFocusToItems();

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

    removeItem(key) {
        if (!this._items.has(key))
            return;

        let item = this._items.get(key);

        item.destroy();

        this._items.delete(key);
    }

    get numItems() {
        return this._items.size;
    }

    clear() {
        this.label.text = '';
        this._box.destroy_all_children();
        this._items.clear();
    }
});
(uuay)kbdA11yDialog.js�/* exported KbdA11yDialog */
const { Clutter, Gio, GObject, Meta } = 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 seat = Clutter.get_default_backend().get_default_seat();
        seat.connect('kbd-a11y-flags-changed',
                     this._showKbdA11yDialog.bind(this));
    }

    _showKbdA11yDialog(seat, newFlags, whatChanged) {
        let dialog = new ModalDialog.ModalDialog();
        let title, description;
        let key, enabled;

        if (whatChanged & Meta.KeyboardA11yFlags.SLOW_KEYS_ENABLED) {
            key = KEY_SLOW_KEYS_ENABLED;
            enabled = (newFlags & Meta.KeyboardA11yFlags.SLOW_KEYS_ENABLED) > 0;
            title = enabled
                ? _("Slow Keys Turned On")
                : _("Slow Keys Turned Off");
            description = _('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 & Meta.KeyboardA11yFlags.STICKY_KEYS_ENABLED) {
            key = KEY_STICKY_KEYS_ENABLED;
            enabled = (newFlags & Meta.KeyboardA11yFlags.STICKY_KEYS_ENABLED) > 0;
            title = enabled
                ? _("Sticky Keys Turned On")
                : _("Sticky Keys Turned Off");
            description = 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 content = new Dialog.MessageDialogContent({ title, description });
        dialog.contentLayout.add_child(content);

        dialog.addButton({
            label: enabled ? _('Leave On') : _('Turn On'),
            action: () => {
                this._a11ySettings.set_boolean(key, true);
                dialog.close();
            },
            default: enabled,
            key: !enabled ? Clutter.KEY_Escape : null,
        });

        dialog.addButton({
            label: enabled ? _('Turn Off') : _('Leave Off'),
            action: () => {
                this._a11ySettings.set_boolean(key, false);
                dialog.close();
            },
            default: !enabled,
            key: enabled ? Clutter.KEY_Escape : null,
        });

        dialog.open();
    }
});
(uuay)thunderbolt.jsk)// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

// the following is a modified version of bolt/contrib/js/client.js

const { Gio, GLib, GObject, 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;
        this.probing = false;
        this._getProxy();
    }

    async _getProxy() {
        let nodeInfo = Gio.DBusNodeInfo.new_for_xml(BoltClientInterface);
        try {
            this._proxy = await 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);
        } catch (e) {
            log(`error creating bolt proxy: ${e.message}`);
            return;
        }
        this._proxy.connectObject('g-properties-changed',
            this._onPropertiesChanged.bind(this), 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.disconnectObject(this);
        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 = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        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;
        this._createPermission();
    }

    async _createPermission() {
        try {
            this._perm = await Polkit.Permission.new('org.freedesktop.bolt.enroll', null, null);
        } catch (e) {
            log(`Failed to get PolKit permission: ${e}`);
        }
    }

    _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.showNotification(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)overviewControls.jsw// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ControlsManager */

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

const AppDisplay = imports.ui.appDisplay;
const Dash = imports.ui.dash;
const Layout = imports.ui.layout;
const Main = imports.ui.main;
const Overview = imports.ui.overview;
const SearchController = imports.ui.searchController;
const Util = imports.misc.util;
const WindowManager = imports.ui.windowManager;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;
const WorkspacesView = imports.ui.workspacesView;

const SMALL_WORKSPACE_RATIO = 0.15;
const DASH_MAX_HEIGHT_RATIO = 0.15;

const A11Y_SCHEMA = 'org.gnome.desktop.a11y.keyboard';

var SIDE_CONTROLS_ANIMATION_TIME = Overview.ANIMATION_TIME;

var ControlsState = {
    HIDDEN: 0,
    WINDOW_PICKER: 1,
    APP_GRID: 2,
};

var ControlsManagerLayout = GObject.registerClass(
class ControlsManagerLayout extends Clutter.BoxLayout {
    _init(searchEntry, appDisplay, workspacesDisplay, workspacesThumbnails,
        searchController, dash, stateAdjustment) {
        super._init({ orientation: Clutter.Orientation.VERTICAL });

        this._appDisplay = appDisplay;
        this._workspacesDisplay = workspacesDisplay;
        this._workspacesThumbnails = workspacesThumbnails;
        this._stateAdjustment = stateAdjustment;
        this._searchEntry = searchEntry;
        this._searchController = searchController;
        this._dash = dash;

        this._cachedWorkspaceBoxes = new Map();
        this._postAllocationCallbacks = [];

        stateAdjustment.connect('notify::value', () => this.layout_changed());

        this._workAreaBox = new Clutter.ActorBox();
        global.display.connectObject(
            'workareas-changed', () => this._updateWorkAreaBox(),
            this);
        this._updateWorkAreaBox();
    }

    _updateWorkAreaBox() {
        const monitor = Main.layoutManager.primaryMonitor;
        if (!monitor)
            return;

        const workArea = Main.layoutManager.getWorkAreaForMonitor(monitor.index);
        const startX = workArea.x - monitor.x;
        const startY = workArea.y - monitor.y;
        this._workAreaBox.set_origin(startX, startY);
        this._workAreaBox.set_size(workArea.width, workArea.height);
    }

    _computeWorkspacesBoxForState(state, box, searchHeight, dashHeight, thumbnailsHeight) {
        const workspaceBox = box.copy();
        const [width, height] = workspaceBox.get_size();
        const { y1: startY } = this._workAreaBox;
        const { spacing } = this;
        const { expandFraction } = this._workspacesThumbnails;

        switch (state) {
        case ControlsState.HIDDEN:
            workspaceBox.set_origin(...this._workAreaBox.get_origin());
            workspaceBox.set_size(...this._workAreaBox.get_size());
            break;
        case ControlsState.WINDOW_PICKER:
            workspaceBox.set_origin(0,
                startY + searchHeight + spacing +
                thumbnailsHeight + spacing * expandFraction);
            workspaceBox.set_size(width,
                height -
                dashHeight - spacing -
                searchHeight - spacing -
                thumbnailsHeight - spacing * expandFraction);
            break;
        case ControlsState.APP_GRID:
            workspaceBox.set_origin(0, startY + searchHeight + spacing);
            workspaceBox.set_size(
                width,
                Math.round(height * SMALL_WORKSPACE_RATIO));
            break;
        }

        return workspaceBox;
    }

    _getAppDisplayBoxForState(state, box, searchHeight, dashHeight, appGridBox) {
        const [width, height] = box.get_size();
        const { y1: startY } = this._workAreaBox;
        const appDisplayBox = new Clutter.ActorBox();
        const { spacing } = this;

        switch (state) {
        case ControlsState.HIDDEN:
        case ControlsState.WINDOW_PICKER:
            appDisplayBox.set_origin(0, box.y2);
            break;
        case ControlsState.APP_GRID:
            appDisplayBox.set_origin(0,
                startY + searchHeight + spacing + appGridBox.get_height());
            break;
        }

        appDisplayBox.set_size(width,
            height -
            searchHeight - spacing -
            appGridBox.get_height() - spacing -
            dashHeight);

        return appDisplayBox;
    }

    _runPostAllocation() {
        if (this._postAllocationCallbacks.length === 0)
            return;

        this._postAllocationCallbacks.forEach(cb => cb());
        this._postAllocationCallbacks = [];
    }

    vfunc_set_container(container) {
        this._container = container;
        if (container)
            this.hookup_style(container);
    }

    vfunc_get_preferred_width(_container, _forHeight) {
        // The MonitorConstraint will allocate us a fixed size anyway
        return [0, 0];
    }

    vfunc_get_preferred_height(_container, _forWidth) {
        // The MonitorConstraint will allocate us a fixed size anyway
        return [0, 0];
    }

    vfunc_allocate(container, box) {
        const childBox = new Clutter.ActorBox();

        const { spacing } = this;

        const startY = this._workAreaBox.y1;
        box.y1 += startY;
        const [width, height] = box.get_size();
        let availableHeight = height;

        // Search entry
        let [searchHeight] = this._searchEntry.get_preferred_height(width);
        childBox.set_origin(0, startY);
        childBox.set_size(width, searchHeight);
        this._searchEntry.allocate(childBox);

        availableHeight -= searchHeight + spacing;

        // Dash
        const maxDashHeight = Math.round(box.get_height() * DASH_MAX_HEIGHT_RATIO);
        this._dash.setMaxSize(width, maxDashHeight);

        let [, dashHeight] = this._dash.get_preferred_height(width);
        dashHeight = Math.min(dashHeight, maxDashHeight);
        childBox.set_origin(0, startY + height - dashHeight);
        childBox.set_size(width, dashHeight);
        this._dash.allocate(childBox);

        availableHeight -= dashHeight + spacing;

        // Workspace Thumbnails
        let thumbnailsHeight = 0;
        if (this._workspacesThumbnails.visible) {
            const { expandFraction } = this._workspacesThumbnails;
            [thumbnailsHeight] =
                this._workspacesThumbnails.get_preferred_height(width);
            thumbnailsHeight = Math.min(
                thumbnailsHeight * expandFraction,
                height * WorkspaceThumbnail.MAX_THUMBNAIL_SCALE);
            childBox.set_origin(0, startY + searchHeight + spacing);
            childBox.set_size(width, thumbnailsHeight);
            this._workspacesThumbnails.allocate(childBox);
        }

        // Workspaces
        let params = [box, searchHeight, dashHeight, thumbnailsHeight];
        const transitionParams = this._stateAdjustment.getStateTransitionParams();

        // Update cached boxes
        for (const state of Object.values(ControlsState)) {
            this._cachedWorkspaceBoxes.set(
                state, this._computeWorkspacesBoxForState(state, ...params));
        }

        let workspacesBox;
        if (!transitionParams.transitioning) {
            workspacesBox = this._cachedWorkspaceBoxes.get(transitionParams.currentState);
        } else {
            const initialBox = this._cachedWorkspaceBoxes.get(transitionParams.initialState);
            const finalBox = this._cachedWorkspaceBoxes.get(transitionParams.finalState);
            workspacesBox = initialBox.interpolate(finalBox, transitionParams.progress);
        }

        this._workspacesDisplay.allocate(workspacesBox);

        // AppDisplay
        if (this._appDisplay.visible) {
            const workspaceAppGridBox =
                this._cachedWorkspaceBoxes.get(ControlsState.APP_GRID);

            params = [box, searchHeight, dashHeight, workspaceAppGridBox];
            let appDisplayBox;
            if (!transitionParams.transitioning) {
                appDisplayBox =
                    this._getAppDisplayBoxForState(transitionParams.currentState, ...params);
            } else {
                const initialBox =
                    this._getAppDisplayBoxForState(transitionParams.initialState, ...params);
                const finalBox =
                    this._getAppDisplayBoxForState(transitionParams.finalState, ...params);

                appDisplayBox = initialBox.interpolate(finalBox, transitionParams.progress);
            }

            this._appDisplay.allocate(appDisplayBox);
        }

        // Search
        childBox.set_origin(0, startY + searchHeight + spacing);
        childBox.set_size(width, availableHeight);

        this._searchController.allocate(childBox);

        this._runPostAllocation();
    }

    ensureAllocation() {
        this.layout_changed();
        return new Promise(
            resolve => this._postAllocationCallbacks.push(resolve));
    }

    getWorkspacesBoxForState(state) {
        return this._cachedWorkspaceBoxes.get(state);
    }
});

var OverviewAdjustment = GObject.registerClass({
    Properties: {
        'gesture-in-progress': GObject.ParamSpec.boolean(
            'gesture-in-progress', 'Gesture in progress', 'Gesture in progress',
            GObject.ParamFlags.READWRITE,
            false),
    },
}, class OverviewAdjustment extends St.Adjustment {
    _init(actor) {
        super._init({
            actor,
            value: ControlsState.WINDOW_PICKER,
            lower: ControlsState.HIDDEN,
            upper: ControlsState.APP_GRID,
        });
    }

    getStateTransitionParams() {
        const currentState = this.value;

        const transition = this.get_transition('value');
        let initialState = transition
            ? transition.get_interval().peek_initial_value()
            : currentState;
        let finalState = transition
            ? transition.get_interval().peek_final_value()
            : currentState;

        if (initialState > finalState) {
            initialState = Math.ceil(initialState);
            finalState = Math.floor(finalState);
        } else {
            initialState = Math.floor(initialState);
            finalState = Math.ceil(finalState);
        }

        const length = Math.abs(finalState - initialState);
        const progress = length > 0
            ? Math.abs((currentState - initialState) / length)
            : 1;

        return {
            transitioning: transition !== null || this.gestureInProgress,
            currentState,
            initialState,
            finalState,
            progress,
        };
    }
});

var ControlsManager = GObject.registerClass(
class ControlsManager extends St.Widget {
    _init() {
        super._init({
            style_class: 'controls-manager',
            x_expand: true,
            y_expand: true,
            clip_to_allocation: true,
        });

        this._ignoreShowAppsButtonToggle = false;

        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._searchEntry.set_offscreen_redirect(Clutter.OffscreenRedirect.ALWAYS);
        this._searchEntryBin = new St.Bin({
            child: this._searchEntry,
            x_align: Clutter.ActorAlign.CENTER,
        });

        this.dash = new Dash.Dash();

        let workspaceManager = global.workspace_manager;
        let activeWorkspaceIndex = workspaceManager.get_active_workspace_index();

        this._workspaceAdjustment = new St.Adjustment({
            actor: this,
            value: activeWorkspaceIndex,
            lower: 0,
            page_increment: 1,
            page_size: 1,
            step_increment: 0,
            upper: workspaceManager.n_workspaces,
        });

        this._stateAdjustment = new OverviewAdjustment(this);
        this._stateAdjustment.connect('notify::value', this._update.bind(this));

        workspaceManager.connectObject(
            'notify::n-workspaces', () => this._updateAdjustment(), this);

        this._searchController = new SearchController.SearchController(
            this._searchEntry,
            this.dash.showAppsButton);
        this._searchController.connect('notify::search-active', this._onSearchChanged.bind(this));

        Main.layoutManager.connect('monitors-changed', () => {
            this._thumbnailsBox.setMonitorIndex(Main.layoutManager.primaryIndex);
        });
        this._thumbnailsBox = new WorkspaceThumbnail.ThumbnailsBox(
            this._workspaceAdjustment, Main.layoutManager.primaryIndex);
        this._thumbnailsBox.connect('notify::should-show', () => {
            this._thumbnailsBox.show();
            this._thumbnailsBox.ease_property('expand-fraction',
                this._thumbnailsBox.should_show ? 1 : 0, {
                    duration: SIDE_CONTROLS_ANIMATION_TIME,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    onComplete: () => this._updateThumbnailsBox(),
                });
        });

        this._workspacesDisplay = new WorkspacesView.WorkspacesDisplay(
            this,
            this._workspaceAdjustment,
            this._stateAdjustment);
        this._appDisplay = new AppDisplay.AppDisplay();

        this.add_child(this._searchEntryBin);
        this.add_child(this._appDisplay);
        this.add_child(this.dash);
        this.add_child(this._searchController);
        this.add_child(this._thumbnailsBox);
        this.add_child(this._workspacesDisplay);

        this.layout_manager = new ControlsManagerLayout(
            this._searchEntryBin,
            this._appDisplay,
            this._workspacesDisplay,
            this._thumbnailsBox,
            this._searchController,
            this.dash,
            this._stateAdjustment);

        this.dash.showAppsButton.connect('notify::checked',
            this._onShowAppsButtonToggled.bind(this));

        Main.ctrlAltTabManager.addGroup(
            this.appDisplay,
            _('Applications'),
            'view-app-grid-symbolic', {
                proxy: this,
                focusCallback: () => {
                    this.dash.showAppsButton.checked = true;
                    this.appDisplay.navigate_focus(
                        null, St.DirectionType.TAB_FORWARD, false);
                },
            });

        Main.ctrlAltTabManager.addGroup(
            this._workspacesDisplay,
            _('Windows'),
            'focus-windows-symbolic', {
                proxy: this,
                focusCallback: () => {
                    this.dash.showAppsButton.checked = false;
                    this._workspacesDisplay.navigate_focus(
                        null, St.DirectionType.TAB_FORWARD, false);
                },
            });

        this._a11ySettings = new Gio.Settings({ schema_id: A11Y_SCHEMA });

        this._lastOverlayKeyTime = 0;
        global.display.connect('overlay-key', () => {
            if (this._a11ySettings.get_boolean('stickykeys-enable'))
                return;

            const { initialState, finalState, transitioning } =
                this._stateAdjustment.getStateTransitionParams();

            const time = GLib.get_monotonic_time() / 1000;
            const timeDiff = time - this._lastOverlayKeyTime;
            this._lastOverlayKeyTime = time;

            const shouldShift = St.Settings.get().enable_animations
                ? transitioning && finalState > initialState
                : Main.overview.visible && timeDiff < Overview.ANIMATION_TIME;

            if (shouldShift)
                this._shiftState(Meta.MotionDirection.UP);
            else
                Main.overview.toggle();
        });

        // connect_after to give search controller first dibs on the event
        global.stage.connect_after('key-press-event', (actor, event) => {
            if (this._searchController.searchActive)
                return Clutter.EVENT_PROPAGATE;

            if (global.stage.key_focus &&
                !this.contains(global.stage.key_focus))
                return Clutter.EVENT_PROPAGATE;

            const { finalState } =
                this._stateAdjustment.getStateTransitionParams();
            let keynavDisplay;

            if (finalState === ControlsState.WINDOW_PICKER)
                keynavDisplay = this._workspacesDisplay;
            else if (finalState === ControlsState.APP_GRID)
                keynavDisplay = this._appDisplay;

            if (!keynavDisplay)
                return Clutter.EVENT_PROPAGATE;

            const symbol = event.get_key_symbol();
            if (symbol === Clutter.KEY_Tab || symbol === Clutter.KEY_Down) {
                keynavDisplay.navigate_focus(
                    null, St.DirectionType.TAB_FORWARD, false);
                return Clutter.EVENT_STOP;
            } else if (symbol === Clutter.KEY_ISO_Left_Tab) {
                keynavDisplay.navigate_focus(
                    null, St.DirectionType.TAB_BACKWARD, false);
                return Clutter.EVENT_STOP;
            }

            return Clutter.EVENT_PROPAGATE;
        });

        Main.wm.addKeybinding(
            'toggle-application-view',
            new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
            this._toggleAppsPage.bind(this));

        Main.wm.addKeybinding('shift-overview-up',
            new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
            () => this._shiftState(Meta.MotionDirection.UP));

        Main.wm.addKeybinding('shift-overview-down',
            new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
            () => this._shiftState(Meta.MotionDirection.DOWN));

        this._update();
    }

    _getFitModeForState(state) {
        switch (state) {
        case ControlsState.HIDDEN:
        case ControlsState.WINDOW_PICKER:
            return WorkspacesView.FitMode.SINGLE;
        case ControlsState.APP_GRID:
            return WorkspacesView.FitMode.ALL;
        default:
            return WorkspacesView.FitMode.SINGLE;
        }
    }

    _getThumbnailsBoxParams() {
        const { initialState, finalState, progress } =
            this._stateAdjustment.getStateTransitionParams();

        const paramsForState = s => {
            let opacity, scale, translationY;
            switch (s) {
            case ControlsState.HIDDEN:
            case ControlsState.WINDOW_PICKER:
                opacity = 255;
                scale = 1;
                translationY = 0;
                break;
            case ControlsState.APP_GRID:
                opacity = 0;
                scale = 0.5;
                translationY = this._thumbnailsBox.height / 2;
                break;
            default:
                opacity = 255;
                scale = 1;
                translationY = 0;
                break;
            }

            return { opacity, scale, translationY };
        };

        const initialParams = paramsForState(initialState);
        const finalParams = paramsForState(finalState);

        return [
            Util.lerp(initialParams.opacity, finalParams.opacity, progress),
            Util.lerp(initialParams.scale, finalParams.scale, progress),
            Util.lerp(initialParams.translationY, finalParams.translationY, progress),
        ];
    }

    _updateThumbnailsBox(animate = false) {
        const { shouldShow } = this._thumbnailsBox;
        const { searchActive } = this._searchController;
        const [opacity, scale, translationY] = this._getThumbnailsBoxParams();

        const thumbnailsBoxVisible = shouldShow && !searchActive && opacity !== 0;
        if (thumbnailsBoxVisible) {
            this._thumbnailsBox.opacity = 0;
            this._thumbnailsBox.visible = thumbnailsBoxVisible;
        }

        const params = {
            opacity: searchActive ? 0 : opacity,
            duration: animate ? SIDE_CONTROLS_ANIMATION_TIME : 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => (this._thumbnailsBox.visible = thumbnailsBoxVisible),
        };

        if (!searchActive) {
            params.scale_x = scale;
            params.scale_y = scale;
            params.translation_y = translationY;
        }

        this._thumbnailsBox.ease(params);
    }

    _updateAppDisplayVisibility(stateTransitionParams = null) {
        if (!stateTransitionParams)
            stateTransitionParams = this._stateAdjustment.getStateTransitionParams();

        const { initialState, finalState } = stateTransitionParams;
        const state = Math.max(initialState, finalState);

        this._appDisplay.visible =
            state > ControlsState.WINDOW_PICKER &&
            !this._searchController.searchActive;
    }

    _update() {
        const params = this._stateAdjustment.getStateTransitionParams();

        const fitMode = Util.lerp(
            this._getFitModeForState(params.initialState),
            this._getFitModeForState(params.finalState),
            params.progress);

        const { fitModeAdjustment } = this._workspacesDisplay;
        fitModeAdjustment.value = fitMode;

        this._updateThumbnailsBox();
        this._updateAppDisplayVisibility(params);
    }

    _onSearchChanged() {
        const { searchActive } = this._searchController;

        if (!searchActive) {
            this._updateAppDisplayVisibility();
            this._workspacesDisplay.reactive = true;
            this._workspacesDisplay.setPrimaryWorkspaceVisible(true);
        } else {
            this._searchController.show();
        }

        this._updateThumbnailsBox(true);

        this._appDisplay.ease({
            opacity: searchActive ? 0 : 255,
            duration: SIDE_CONTROLS_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this._updateAppDisplayVisibility(),
        });
        this._workspacesDisplay.ease({
            opacity: searchActive ? 0 : 255,
            duration: SIDE_CONTROLS_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._workspacesDisplay.reactive = !searchActive;
                this._workspacesDisplay.setPrimaryWorkspaceVisible(!searchActive);
            },
        });
        this._searchController.ease({
            opacity: searchActive ? 255 : 0,
            duration: SIDE_CONTROLS_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => (this._searchController.visible = searchActive),
        });
    }

    _onShowAppsButtonToggled() {
        if (this._ignoreShowAppsButtonToggle)
            return;

        const checked = this.dash.showAppsButton.checked;

        const value = checked
            ? ControlsState.APP_GRID : ControlsState.WINDOW_PICKER;
        this._stateAdjustment.remove_transition('value');
        this._stateAdjustment.ease(value, {
            duration: SIDE_CONTROLS_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    _toggleAppsPage() {
        if (Main.overview.visible) {
            const checked = this.dash.showAppsButton.checked;
            this.dash.showAppsButton.checked = !checked;
        } else {
            Main.overview.show(ControlsState.APP_GRID);
        }
    }

    _shiftState(direction) {
        let { currentState, finalState } = this._stateAdjustment.getStateTransitionParams();

        if (direction === Meta.MotionDirection.DOWN)
            finalState = Math.max(finalState - 1, ControlsState.HIDDEN);
        else if (direction === Meta.MotionDirection.UP)
            finalState = Math.min(finalState + 1, ControlsState.APP_GRID);

        if (finalState === currentState)
            return;

        if (currentState === ControlsState.HIDDEN &&
            finalState === ControlsState.WINDOW_PICKER) {
            Main.overview.show();
        } else if (finalState === ControlsState.HIDDEN) {
            Main.overview.hide();
        } else {
            this._stateAdjustment.ease(finalState, {
                duration: SIDE_CONTROLS_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    this.dash.showAppsButton.checked =
                        finalState === ControlsState.APP_GRID;
                },
            });
        }
    }

    _updateAdjustment() {
        let workspaceManager = global.workspace_manager;
        let newNumWorkspaces = workspaceManager.n_workspaces;
        let activeIndex = workspaceManager.get_active_workspace_index();

        this._workspaceAdjustment.upper = newNumWorkspaces;

        // A workspace might have been inserted or removed before the active
        // one, causing the adjustment to go out of sync, so update the value
        this._workspaceAdjustment.remove_transition('value');
        this._workspaceAdjustment.value = activeIndex;
    }

    vfunc_unmap() {
        super.vfunc_unmap();
        this._workspacesDisplay.hide();
    }

    prepareToEnterOverview() {
        this._searchController.prepareToEnterOverview();
        this._workspacesDisplay.prepareToEnterOverview();
    }

    prepareToLeaveOverview() {
        this._workspacesDisplay.prepareToLeaveOverview();
    }

    animateToOverview(state, callback) {
        this._ignoreShowAppsButtonToggle = true;

        this._stateAdjustment.value = ControlsState.HIDDEN;
        this._stateAdjustment.ease(state, {
            duration: Overview.ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => {
                if (callback)
                    callback();
            },
        });

        this.dash.showAppsButton.checked =
            state === ControlsState.APP_GRID;

        this._ignoreShowAppsButtonToggle = false;
    }

    animateFromOverview(callback) {
        this._ignoreShowAppsButtonToggle = true;

        this._stateAdjustment.ease(ControlsState.HIDDEN, {
            duration: Overview.ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => {
                this.dash.showAppsButton.checked = false;
                this._ignoreShowAppsButtonToggle = false;

                if (callback)
                    callback();
            },
        });
    }

    getWorkspacesBoxForState(state) {
        return this.layoutManager.getWorkspacesBoxForState(state);
    }

    gestureBegin(tracker) {
        const baseDistance = global.screen_height;
        const progress = this._stateAdjustment.value;
        const points = [
            ControlsState.HIDDEN,
            ControlsState.WINDOW_PICKER,
            ControlsState.APP_GRID,
        ];

        const transition = this._stateAdjustment.get_transition('value');
        const cancelProgress = transition
            ? transition.get_interval().peek_final_value()
            : Math.round(progress);
        this._stateAdjustment.remove_transition('value');

        tracker.confirmSwipe(baseDistance, points, progress, cancelProgress);
        this.prepareToEnterOverview();
        this._stateAdjustment.gestureInProgress = true;
    }

    gestureProgress(progress) {
        this._stateAdjustment.value = progress;
    }

    gestureEnd(target, duration, onComplete) {
        if (target === ControlsState.HIDDEN)
            this.prepareToLeaveOverview();

        this.dash.showAppsButton.checked =
            target === ControlsState.APP_GRID;

        this._stateAdjustment.remove_transition('value');
        this._stateAdjustment.ease(target, {
            duration,
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            onComplete,
        });

        this._stateAdjustment.gestureInProgress = false;
    }

    async runStartupAnimation(callback) {
        this._ignoreShowAppsButtonToggle = true;

        this.prepareToEnterOverview();

        this._stateAdjustment.value = ControlsState.HIDDEN;
        this._stateAdjustment.ease(ControlsState.WINDOW_PICKER, {
            duration: Overview.ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        this.dash.showAppsButton.checked = false;
        this._ignoreShowAppsButtonToggle = false;

        // Set the opacity here to avoid a 1-frame flicker
        this.opacity = 0;

        // We can't run the animation before the first allocation happens
        await this.layout_manager.ensureAllocation();

        const { STARTUP_ANIMATION_TIME } = Layout;

        // Opacity
        this.ease({
            opacity: 255,
            duration: STARTUP_ANIMATION_TIME,
            mode: Clutter.AnimationMode.LINEAR,
        });

        // Search bar falls from the ceiling
        const { primaryMonitor } = Main.layoutManager;
        const [, y] = this._searchEntryBin.get_transformed_position();
        const yOffset = y - primaryMonitor.y;

        this._searchEntryBin.translation_y = -(yOffset + this._searchEntryBin.height);
        this._searchEntryBin.ease({
            translation_y: 0,
            duration: STARTUP_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });

        // The Dash rises from the bottom. This is the last animation to finish,
        // so run the callback there.
        this.dash.translation_y = this.dash.height + this.dash.margin_bottom;
        this.dash.ease({
            translation_y: 0,
            delay: STARTUP_ANIMATION_TIME,
            duration: STARTUP_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => callback(),
        });
    }

    get searchEntry() {
        return this._searchEntry;
    }

    get appDisplay() {
        return this._appDisplay;
    }
});
(uuay)modalDialog.js	"// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ModalDialog */

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

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;

var OPEN_AND_CLOSE_TIME = 100;
var FADE_OUT_DIALOG_TIME = 1000;

var State = {
    OPENED: 0,
    CLOSED: 1,
    OPENING: 2,
    CLOSING: 3,
    FADED_OUT: 4,
};

var ModalDialog = GObject.registerClass({
    Properties: {
        'state': GObject.ParamSpec.int('state', 'Dialog state', 'state',
                                       GObject.ParamFlags.READABLE,
                                       Math.min(...Object.values(State)),
                                       Math.max(...Object.values(State)),
                                       State.CLOSED),
    },
    Signals: { 'opened': {}, 'closed': {} },
}, class ModalDialog extends St.Widget {
    _init(params) {
        super._init({
            visible: false,
            reactive: true,
            x: 0,
            y: 0,
            accessible_role: Atk.Role.DIALOG,
        });

        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;

        Main.layoutManager.modalDialogGroup.add_actor(this);

        const constraint = new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        });
        this.add_constraint(constraint);

        this.backgroundStack = new St.Widget({
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });
        this._backgroundBin = new St.Bin({ child: this.backgroundStack });
        this._monitorConstraint = new Layout.MonitorConstraint();
        this._backgroundBin.add_constraint(this._monitorConstraint);
        this.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, {
                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;
    }

    get state() {
        return this._state;
    }

    _setState(state) {
        if (this._state == state)
            return;

        this._state = state;
        this.notify('state');
    }

    vfunc_key_press_event() {
        if (global.focus_manager.navigate_from_event(Clutter.get_current_event()))
            return Clutter.EVENT_STOP;

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_captured_event(event) {
        if (Main.keyboard.maybeHandleEvent(event))
            return Clutter.EVENT_STOP;

        return Clutter.EVENT_PROPAGATE;
    }

    clearButtons() {
        this.dialogLayout.clearButtons();
    }

    setButtons(buttons) {
        this.clearButtons();

        for (let buttonInfo of buttons)
            this.addButton(buttonInfo);
    }

    addButton(buttonInfo) {
        return this.dialogLayout.addButton(buttonInfo);
    }

    _fadeOpen(onPrimary) {
        if (onPrimary)
            this._monitorConstraint.primary = true;
        else
            this._monitorConstraint.index = global.display.get_current_monitor();

        this._setState(State.OPENING);

        this.dialogLayout.opacity = 255;
        if (this._lightbox)
            this._lightbox.lightOn();
        this.opacity = 0;
        this.show();
        this.ease({
            opacity: 255,
            duration: this._shouldFadeIn ? OPEN_AND_CLOSE_TIME : 0,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._setState(State.OPENED);
                this.emit('opened');
            },
        });
    }

    setInitialKeyFocus(actor) {
        this._initialKeyFocus?.disconnectObject(this);

        this._initialKeyFocus = actor;

        actor.connectObject('destroy',
            () => (this._initialKeyFocus = null), this);
    }

    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._setState(State.CLOSED);
        this.hide();
        this.emit('closed');

        if (this._destroyOnClose)
            this.destroy();
    }

    close(timestamp) {
        if (this.state == State.CLOSED || this.state == State.CLOSING)
            return;

        this._setState(State.CLOSING);
        this.popModal(timestamp);
        this._savedKeyFocus = null;

        if (this._shouldFadeOut) {
            this.ease({
                opacity: 0,
                duration: OPEN_AND_CLOSE_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => this._closeComplete(),
            });
        } 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.contains(focus))
            this._savedKeyFocus = focus;
        else
            this._savedKeyFocus = null;
        Main.popModal(this._grab, timestamp);
        this._grab = null;
        this._hasModal = false;

        if (!this._shellReactive)
            this.backgroundStack.set_child_above_sibling(this._eventBlocker, null);
    }

    pushModal(timestamp) {
        if (this._hasModal)
            return true;

        let params = { actionMode: this._actionMode };
        if (timestamp)
            params['timestamp'] = timestamp;
        let grab = Main.pushModal(this, params);
        if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
            Main.popModal(grab);
            return false;
        }

        this._grab = grab;
        Main.layoutManager.emit('system-modal-opened');

        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.backgroundStack.set_child_below_sibling(this._eventBlocker, null);
        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 response 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
    // immediately, 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);
        this.dialogLayout.ease({
            opacity: 0,
            duration: FADE_OUT_DIALOG_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => (this.state = State.FADED_OUT),
        });
    }
});
(uuay)status/UbL�T�<�hkYbarLevel.js�"/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported BarLevel */

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

var BarLevel = GObject.registerClass({
    Properties: {
        'value': GObject.ParamSpec.double(
            'value', 'value', 'value',
            GObject.ParamFlags.READWRITE,
            0, 2, 0),
        'maximum-value': GObject.ParamSpec.double(
            'maximum-value', 'maximum-value', 'maximum-value',
            GObject.ParamFlags.READWRITE,
            1, 2, 1),
        'overdrive-start': GObject.ParamSpec.double(
            'overdrive-start', 'overdrive-start', 'overdrive-start',
            GObject.ParamFlags.READWRITE,
            1, 2, 1),
    },
}, class BarLevel extends St.DrawingArea {
    _init(params) {
        this._maxValue = 1;
        this._value = 0;
        this._overdriveStart = 1;
        this._barLevelWidth = 0;

        let defaultParams = {
            style_class: 'barlevel',
            accessible_role: Atk.Role.LEVEL_BAR,
        };
        super._init(Object.assign(defaultParams, params));
        this.connect('notify::allocation', () => {
            this._barLevelWidth = this.allocation.get_width();
        });

        this._customAccessible = St.GenericAccessible.new_for_actor(this);
        this.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('notify::value', this._valueChanged.bind(this));
    }

    get value() {
        return this._value;
    }

    set value(value) {
        value = Math.max(Math.min(value, this._maxValue), 0);

        if (this._value == value)
            return;

        this._value = value;
        this.notify('value');
        this.queue_repaint();
    }

    get maximumValue() {
        return this._maxValue;
    }

    set maximumValue(value) {
        value = Math.max(value, 1);

        if (this._maxValue == value)
            return;

        this._maxValue = value;
        this._overdriveStart = Math.min(this._overdriveStart, this._maxValue);
        this.notify('maximum-value');
        this.queue_repaint();
    }

    get overdriveStart() {
        return this._overdriveStart;
    }

    set overdriveStart(value) {
        if (this._overdriveStart == value)
            return;

        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.notify('overdrive-start');
        this.queue_repaint();
    }

    vfunc_repaint() {
        let cr = this.get_context();
        let themeNode = this.get_theme_node();
        let [width, height] = this.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() {
        return this._value;
    }

    _getOverdriveStart() {
        return this._overdriveStart;
    }

    _getMinimumValue() {
        return 0;
    }

    _getMaximumValue() {
        return this._maxValue;
    }

    _setCurrentValue(_actor, value) {
        this._value = value;
    }

    _valueChanged() {
        this._customAccessible.notify("accessible-value");
    }
});
(uuay)components/c|yr
>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() {
        this._aggregateProvider = Provider(Gio.DBus.system,
                                           'org.freedesktop.realmd',
                                           '/org/freedesktop/realmd',
                                           this._reloadRealms.bind(this));
        this._realms = {};
        this._loginFormat = null;

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

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

        if (!realmPaths)
            return;

        for (let i = 0; i < realmPaths.length; i++) {
            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.disconnectObject(this);
        this._realms = { };
        this._updateLoginFormat();
    }
};
Signals.addSignalMethods(Manager.prototype);
(uuay)keyring.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */

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

const Dialog = imports.ui.dialog;
const ModalDialog = imports.ui.modalDialog;
const ShellEntry = imports.ui.shellEntry;
const CheckBox = imports.ui.checkBox;
const Util = imports.misc.util;

var KeyringDialog = GObject.registerClass(
class KeyringDialog extends ModalDialog.ModalDialog {
    _init() {
        super._init({ 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 content = new Dialog.MessageDialogContent();

        this.prompt.bind_property('message',
            content, 'title', GObject.BindingFlags.SYNC_CREATE);
        this.prompt.bind_property('description',
            content, 'description', GObject.BindingFlags.SYNC_CREATE);

        let passwordBox = new St.BoxLayout({
            style_class: 'prompt-dialog-password-layout',
            vertical: true,
        });

        this._passwordEntry = new St.PasswordEntry({
            style_class: 'prompt-dialog-password-entry',
            can_focus: true,
            x_align: Clutter.ActorAlign.CENTER,
        });
        ShellEntry.addContextMenu(this._passwordEntry);
        this._passwordEntry.clutter_text.connect('activate', this._onPasswordActivate.bind(this));
        this.prompt.bind_property('password-visible',
            this._passwordEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
        passwordBox.add_child(this._passwordEntry);

        this._confirmEntry = new St.PasswordEntry({
            style_class: 'prompt-dialog-password-entry',
            can_focus: true,
            x_align: Clutter.ActorAlign.CENTER,
        });
        ShellEntry.addContextMenu(this._confirmEntry);
        this._confirmEntry.clutter_text.connect('activate', this._onConfirmActivate.bind(this));
        this.prompt.bind_property('confirm-visible',
            this._confirmEntry, 'visible', GObject.BindingFlags.SYNC_CREATE);
        passwordBox.add_child(this._confirmEntry);

        this.prompt.set_password_actor(this._passwordEntry.clutter_text);
        this.prompt.set_confirm_actor(this._confirmEntry.clutter_text);

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

        let capsLockWarning = new ShellEntry.CapsLockWarning();
        let syncCapsLockWarningVisibility = () => {
            capsLockWarning.visible =
                this.prompt.password_visible || this.prompt.confirm_visible;
        };
        this.prompt.connect('notify::password-visible', syncCapsLockWarningVisibility);
        this.prompt.connect('notify::confirm-visible', syncCapsLockWarningVisibility);
        warningBox.add_child(capsLockWarning);

        let warning = new St.Label({ style_class: 'prompt-dialog-error-label' });
        warning.clutter_text.ellipsize = Pango.EllipsizeMode.NONE;
        warning.clutter_text.line_wrap = true;
        this.prompt.bind_property('warning',
            warning, 'text', GObject.BindingFlags.SYNC_CREATE);
        this.prompt.connect('notify::warning-visible', () => {
            warning.opacity = this.prompt.warning_visible ? 255 : 0;
        });
        this.prompt.connect('notify::warning', () => {
            if (this._passwordEntry && this.prompt.warning !== '')
                Util.wiggle(this._passwordEntry);
        });
        warningBox.add_child(warning);

        passwordBox.add_child(warningBox);
        content.add_child(passwordBox);

        this._choice = new CheckBox.CheckBox();
        this.prompt.bind_property('choice-label', this._choice.getLabelActor(),
            'text', GObject.BindingFlags.SYNC_CREATE);
        this.prompt.bind_property('choice-chosen', this._choice,
            'checked', GObject.BindingFlags.SYNC_CREATE | GObject.BindingFlags.BIDIRECTIONAL);
        this.prompt.bind_property('choice-visible', this._choice,
            'visible', GObject.BindingFlags.SYNC_CREATE);
        content.add_child(this._choice);

        this.contentLayout.add_child(content);

        this._cancelButton = this.addButton({
            label: '',
            action: this._onCancelButton.bind(this),
            key: Clutter.KEY_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);
    }

    _updateSensitivity(sensitive) {
        if (this._passwordEntry)
            this._passwordEntry.reactive = sensitive;

        if (this._confirmEntry)
            this._confirmEntry.reactive = sensitive;

        this._continueButton.can_focus = sensitive;
        this._continueButton.reactive = 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() {
        this._ensureOpen();
        this._updateSensitivity(true);
        this._passwordEntry.text = '';
        this._passwordEntry.grab_key_focus();
    }

    _onShowConfirm() {
        this._ensureOpen();
        this._updateSensitivity(true);
        this._confirmEntry.text = '';
        this._continueButton.grab_key_focus();
    }

    _onHidePrompt() {
        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 = GObject.registerClass(
class KeyringPrompter extends Gcr.SystemPrompter {
    _init() {
        super._init();
        this.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.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.prompting)
            this._currentPrompt.cancel();
        this._currentPrompt = null;
    }
});

var Component = KeyringPrompter;
(uuay)history.jsW// -*- 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) {
        input = input.trim();
        if (input &&
            (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;
        return input; // trimmed
    }

    _onEntryKeyPress(entry, event) {
        let symbol = event.get_key_symbol();
        if (symbol === Clutter.KEY_Up)
            return this._setPrevItem(entry.get_text().trim());
        else if (symbol === Clutter.KEY_Down)
            return this._setNextItem(entry.get_text().trim());

        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)overview.js�U// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Overview, ANIMATION_TIME */

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

// Time for initial animation going into Overview mode;
// this is defined here to make it available in imports.
var ANIMATION_TIME = 250;

const DND = imports.ui.dnd;
const LayoutManager = imports.ui.layout;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const OverviewControls = imports.ui.overviewControls;
const Params = imports.misc.params;
const SwipeTracker = imports.ui.swipeTracker;
const WindowManager = imports.ui.windowManager;
const WorkspaceThumbnail = imports.ui.workspaceThumbnail;

var DND_WINDOW_SWITCH_TIMEOUT = 750;

var OVERVIEW_ACTIVATION_TIMEOUT = 0.5;

var ShellInfo = class {
    constructor() {
        this._source = null;
    }

    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 });
        }

        if (undoCallback)
            notification.addAction(_('Undo'), () => undoCallback());

        this._source.showNotification(notification);
    }
};

var OverviewActor = GObject.registerClass(
class OverviewActor extends St.BoxLayout {
    _init() {
        super._init({
            name: 'overview',
            /* Translators: This is the main view to select
                activities. See also note for "Activities" string. */
            accessible_name: _("Overview"),
            vertical: true,
        });

        this.add_constraint(new LayoutManager.MonitorConstraint({ primary: true }));

        this._controls = new OverviewControls.ControlsManager();
        this.add_child(this._controls);
    }

    prepareToEnterOverview() {
        this._controls.prepareToEnterOverview();
    }

    prepareToLeaveOverview() {
        this._controls.prepareToLeaveOverview();
    }

    animateToOverview(state, callback) {
        this._controls.animateToOverview(state, callback);
    }

    animateFromOverview(callback) {
        this._controls.animateFromOverview(callback);
    }

    runStartupAnimation(callback) {
        this._controls.runStartupAnimation(callback);
    }

    get dash() {
        return this._controls.dash;
    }

    get searchEntry() {
        return this._controls.searchEntry;
    }

    get controls() {
        return this._controls;
    }
});

const OverviewShownState = {
    HIDDEN: 'HIDDEN',
    HIDING: 'HIDING',
    SHOWING: 'SHOWING',
    SHOWN: 'SHOWN',
};

const OVERVIEW_SHOWN_TRANSITIONS = {
    [OverviewShownState.HIDDEN]: {
        signal: 'hidden',
        allowedTransitions: [OverviewShownState.SHOWING],
    },
    [OverviewShownState.HIDING]: {
        signal: 'hiding',
        allowedTransitions:
            [OverviewShownState.HIDDEN, OverviewShownState.SHOWING],
    },
    [OverviewShownState.SHOWING]: {
        signal: 'showing',
        allowedTransitions:
            [OverviewShownState.SHOWN, OverviewShownState.HIDING],
    },
    [OverviewShownState.SHOWN]: {
        signal: 'shown',
        allowedTransitions: [OverviewShownState.HIDING],
    },
};

var Overview = class {
    constructor() {
        this._initCalled = false;
        this._visible = false;

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

    get dash() {
        return this._overview.dash;
    }

    get dashIconSize() {
        logError(new Error('Usage of Overview.\'dashIconSize\' is deprecated, ' +
            'use \'dash.iconSize\' property instead'));
        return this.dash.iconSize;
    }

    get animationInProgress() {
        return this._animationInProgress;
    }

    get visible() {
        return this._visible;
    }

    get visibleTarget() {
        return this._visibleTarget;
    }

    get closing() {
        return this._animationInProgress && !this._visibleTarget;
    }

    _createOverview() {
        if (this._overview)
            return;

        if (this.isDummy)
            return;

        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;
        this._shownState = OverviewShownState.HIDDEN;

        // 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', (_actor, event) => {
            return event.type() === Clutter.EventType.ENTER ||
                event.type() === Clutter.EventType.LEAVE
                ? Clutter.EVENT_PROPAGATE : Clutter.EVENT_STOP;
        });
        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();
    }

    _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._overview = new OverviewActor();
        this._overview._delegate = this;
        Main.layoutManager.overviewGroup.add_child(this._overview);

        this._shellInfo = new ShellInfo();

        Main.layoutManager.connect('monitors-changed', this._relayout.bind(this));
        this._relayout();

        Main.wm.addKeybinding(
            'toggle-overview',
            new Gio.Settings({ schema_id: WindowManager.SHELL_KEYBINDINGS_SCHEMA }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
            this.toggle.bind(this));

        const swipeTracker = new SwipeTracker.SwipeTracker(global.stage,
            Clutter.Orientation.VERTICAL,
            Shell.ActionMode.NORMAL | Shell.ActionMode.OVERVIEW,
            { allowDrag: false, allowScroll: false });
        swipeTracker.orientation = Clutter.Orientation.VERTICAL;
        swipeTracker.connect('begin', this._gestureBegin.bind(this));
        swipeTracker.connect('update', this._gestureUpdate.bind(this));
        swipeTracker.connect('end', this._gestureEnd.bind(this));
        this._swipeTracker = swipeTracker;
    }

    //
    // 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);
    }

    _changeShownState(state) {
        const {allowedTransitions} =
            OVERVIEW_SHOWN_TRANSITIONS[this._shownState];

        if (!allowedTransitions.includes(state)) {
            throw new Error('Invalid overview shown transition from ' +
                `${this._shownState} to ${state}`);
        }

        this._shownState = state;
        this.emit(OVERVIEW_SHOWN_TRANSITIONS[state].signal);
    }

    _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() {
        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(global.get_current_time());
            this.hide();
        }
        this._resetWindowSwitchTimeout();
        this._lastHoveredWindow = null;
        DND.removeDragMonitor(this._dragMonitor);
        this.endItemDrag();
    }

    _resetWindowSwitchTimeout() {
        if (this._windowSwitchTimeoutId != 0) {
            GLib.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 = GLib.timeout_add(
                GLib.PRIORITY_DEFAULT,
                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;
    }

    _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);
    }

    _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);
    }

    _gestureBegin(tracker) {
        this._overview.controls.gestureBegin(tracker);
    }

    _gestureUpdate(tracker, progress) {
        if (!this._shown) {
            Meta.disable_unredirect_for_display(global.display);

            this._shown = true;
            this._visible = true;
            this._visibleTarget = true;
            this._animationInProgress = true;

            Main.layoutManager.overviewGroup.set_child_above_sibling(
                this._coverPane, null);
            this._coverPane.show();
            this._changeShownState(OverviewShownState.SHOWING);

            Main.layoutManager.showOverview();
            this._syncGrab();
        }

        this._overview.controls.gestureProgress(progress);
    }

    _gestureEnd(tracker, duration, endProgress) {
        let onComplete;
        if (endProgress === 0) {
            this._shown = false;
            this._visibleTarget = false;
            this._changeShownState(OverviewShownState.HIDING);
            Main.panel.style = `transition-duration: ${duration}ms;`;
            onComplete = () => this._hideDone();
        } else {
            onComplete = () => this._showDone();
        }

        this._overview.controls.gestureEnd(endProgress, duration, onComplete);
    }

    beginItemDrag(source) {
        this.emit('item-drag-begin', source);
        this._inItemDrag = true;
    }

    cancelledItemDrag(source) {
        this.emit('item-drag-cancelled', source);
    }

    endItemDrag(source) {
        if (!this._inItemDrag)
            return;
        this.emit('item-drag-end', source);
        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._overview.searchEntry.grab_key_focus();
    }

    // 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 ||
            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 && !this._modal) {
                if (global.display.get_grab_op() !== Meta.GrabOp.NONE &&
                    global.display.get_grab_op() !== Meta.GrabOp.WAYLAND_POPUP) {
                    this.hide();
                    return false;
                }

                const grab = Main.pushModal(global.stage, {
                    actionMode: Shell.ActionMode.OVERVIEW,
                });
                if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
                    Main.popModal(grab);
                    this.hide();
                    return false;
                }

                this._grab = grab;
                this._modal = true;
            }
        } else {
            // eslint-disable-next-line no-lonely-if
            if (this._modal) {
                Main.popModal(this._grab);
                this._grab = false;
                this._modal = false;
            }
        }
        return true;
    }

    // show:
    //
    // Animates the overview visible and grabs mouse and keyboard input
    show(state = OverviewControls.ControlsState.WINDOW_PICKER) {
        if (state === OverviewControls.ControlsState.HIDDEN)
            throw new Error('Invalid state, use hide() to hide');

        if (this.isDummy)
            return;
        if (this._shown)
            return;
        this._shown = true;

        if (!this._syncGrab())
            return;

        Main.layoutManager.showOverview();
        this._animateVisible(state);
    }


    _animateVisible(state) {
        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);

        Main.layoutManager.overviewGroup.set_child_above_sibling(
            this._coverPane, null);
        this._coverPane.show();

        this._overview.prepareToEnterOverview();
        this._changeShownState(OverviewShownState.SHOWING);
        this._overview.animateToOverview(state, () => this._showDone());
    }

    _showDone() {
        this._animationInProgress = false;
        this._coverPane.hide();

        if (this._shownState !== OverviewShownState.SHOWN)
            this._changeShownState(OverviewShownState.SHOWN);

        // Handle any calls to hide* while we were showing
        if (!this._shown)
            this._animateNotVisible();

        this._syncGrab();
    }

    // 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;

        Main.layoutManager.overviewGroup.set_child_above_sibling(
            this._coverPane, null);
        this._coverPane.show();

        this._overview.prepareToLeaveOverview();
        this._changeShownState(OverviewShownState.HIDING);
        this._overview.animateFromOverview(() => this._hideDone());
    }

    _hideDone() {
        // Re-enable unredirection
        Meta.enable_unredirect_for_display(global.display);

        this._coverPane.hide();

        this._visible = false;
        this._animationInProgress = false;

        // Handle any calls to show* while we were hiding
        if (this._shown) {
            this._changeShownState(OverviewShownState.HIDDEN);
            this._animateVisible(OverviewControls.ControlsState.WINDOW_PICKER);
        } else {
            Main.layoutManager.hideOverview();
            this._changeShownState(OverviewShownState.HIDDEN);
        }

        Main.panel.style = null;

        this._syncGrab();
    }

    toggle() {
        if (this.isDummy)
            return;

        if (this._visible)
            this.hide();
        else
            this.show();
    }

    showApps() {
        this.show(OverviewControls.ControlsState.APP_GRID);
    }

    selectApp(id) {
        this.showApps();
        this._overview.controls.appDisplay.selectApp(id);
    }

    runStartupAnimation(callback) {
        Main.panel.style = 'transition-duration: 0ms;';

        this._shown = true;
        this._visible = true;
        this._visibleTarget = true;
        Main.layoutManager.showOverview();
        // We should call this._syncGrab() here, but moved it to happen after
        // the animation because of a race in the xserver where the grab
        // fails when requested very early during startup.

        Meta.disable_unredirect_for_display(global.display);

        this._changeShownState(OverviewShownState.SHOWING);

        this._overview.runStartupAnimation(() => {
            // Overview got hidden during startup animation
            if (this._shownState !== OverviewShownState.SHOWING) {
                callback();
                return;
            }

            if (!this._syncGrab()) {
                callback();
                this.hide();
                return;
            }

            Main.panel.style = null;
            this._changeShownState(OverviewShownState.SHOWN);
            callback();
        });
    }

    getShowAppsButton() {
        logError(new Error('Usage of Overview.\'getShowAppsButton\' is deprecated, ' +
            'use \'dash.showAppsButton\' property instead'));

        return this.dash.showAppsButton;
    }

    get searchEntry() {
        return this._overview.searchEntry;
    }
};
Signals.addSignalMethods(Overview.prototype);
(uuay)focusCaretTracker.js@/** -*- 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)pointerA11yTimeout.js�/* exported PointerA11yTimeout */
const { Clutter, GObject, Meta, St } = imports.gi;
const Main = imports.ui.main;
const Cairo = imports.cairo;

const SUCCESS_ZOOM_OUT_DURATION = 150;

var PieTimer = GObject.registerClass({
    Properties: {
        'angle': GObject.ParamSpec.double(
            'angle', 'angle', 'angle',
            GObject.ParamFlags.READWRITE,
            0, 2 * Math.PI, 0),
    },
}, class PieTimer extends St.DrawingArea {
    _init() {
        this._angle = 0;
        super._init({
            style_class: 'pie-timer',
            opacity: 0,
            visible: false,
            can_focus: false,
            reactive: false,
        });

        this.set_pivot_point(0.5, 0.5);
    }

    get angle() {
        return this._angle;
    }

    set angle(angle) {
        if (this._angle == angle)
            return;

        this._angle = angle;
        this.notify('angle');
        this.queue_repaint();
    }

    vfunc_repaint() {
        let node = this.get_theme_node();
        let backgroundColor = node.get_color('-pie-background-color');
        let borderColor = node.get_color('-pie-border-color');
        let borderWidth = node.get_length('-pie-border-width');
        let [width, height] = this.get_surface_size();
        let radius = Math.min(width / 2, height / 2);

        let startAngle = 3 * Math.PI / 2;
        let endAngle = startAngle + this._angle;

        let cr = this.get_context();
        cr.setLineCap(Cairo.LineCap.ROUND);
        cr.setLineJoin(Cairo.LineJoin.ROUND);
        cr.translate(width / 2, height / 2);

        if (this._angle < 2 * Math.PI)
            cr.moveTo(0, 0);

        cr.arc(0, 0, radius - borderWidth, startAngle, endAngle);

        if (this._angle < 2 * Math.PI)
            cr.lineTo(0, 0);

        cr.closePath();

        cr.setLineWidth(0);
        Clutter.cairo_set_source_color(cr, backgroundColor);
        cr.fillPreserve();

        cr.setLineWidth(borderWidth);
        Clutter.cairo_set_source_color(cr, borderColor);
        cr.stroke();

        cr.$dispose();
    }

    start(x, y, duration) {
        this.x = x - this.width / 2;
        this.y = y - this.height / 2;
        this.show();

        this.ease({
            opacity: 255,
            duration: duration / 4,
            mode: Clutter.AnimationMode.EASE_IN_QUAD,
        });

        this.ease_property('angle', 2 * Math.PI, {
            duration,
            mode: Clutter.AnimationMode.LINEAR,
            onComplete: this._onTransitionComplete.bind(this),
        });
    }

    _onTransitionComplete() {
        this.ease({
            scale_x: 2,
            scale_y: 2,
            opacity: 0,
            duration: SUCCESS_ZOOM_OUT_DURATION,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => this.destroy(),
        });
    }
});

var PointerA11yTimeout = class PointerA11yTimeout {
    constructor() {
        let seat = Clutter.get_default_backend().get_default_seat();

        seat.connect('ptr-a11y-timeout-started', (o, device, type, timeout) => {
            let [x, y] = global.get_pointer();

            this._pieTimer = new PieTimer();
            Main.uiGroup.add_actor(this._pieTimer);
            Main.uiGroup.set_child_above_sibling(this._pieTimer, null);

            this._pieTimer.start(x, y, timeout);

            if (type == Clutter.PointerA11yTimeoutType.GESTURE)
                global.display.set_cursor(Meta.Cursor.CROSSHAIR);
        });

        seat.connect('ptr-a11y-timeout-stopped', (o, device, type, clicked) => {
            if (!clicked)
                this._pieTimer.destroy();

            if (type == Clutter.PointerA11yTimeoutType.GESTURE)
                global.display.set_cursor(Meta.Cursor.DEFAULT);
        });
    }
};
(uuay)layout.jsC�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported MonitorConstraint, LayoutManager */

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 DND = imports.ui.dnd;
const Main = imports.ui.main;
const Params = imports.misc.params;
const Ripples = imports.ui.ripples;

var STARTUP_ANIMATION_TIME = 500;
var BACKGROUND_FADE_ANIMATION_TIME = 1000;

var HOT_CORNER_PRESSURE_THRESHOLD = 100; // pixels
var HOT_CORNER_PRESSURE_TIMEOUT = 1000; // ms

const SCREEN_TRANSITION_DELAY = 250; // ms
const SCREEN_TRANSITION_DURATION = 500; // 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 workArea() {
        return this._workArea;
    }

    set workArea(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, geometryScale) {
        this.index = index;
        this.x = geometry.x;
        this.y = geometry.y;
        this.width = geometry.width;
        this.height = geometry.height;
        this.geometry_scale = geometryScale;
    }

    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': {},
        'system-modal-opened': {},
    },
}, 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;

        // 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.add_child(this.uiGroup);

        global.stage.remove_actor(global.window_group);
        this.uiGroup.add_actor(global.window_group);

        // Using addChrome() to add actors to uiGroup will position actors
        // underneath the top_window_group.
        // To insert actors at the top of uiGroup, we use addTopChrome() or
        // add the actor directly using uiGroup.add_actor().
        global.stage.remove_actor(global.top_window_group);
        this.uiGroup.add_actor(global.top_window_group);

        this.overviewGroup = new St.Widget({
            name: 'overviewGroup',
            visible: false,
            reactive: true,
            constraints: new Clutter.BindConstraint({
                source: this.uiGroup,
                coordinate: Clutter.BindCoordinate.ALL,
            }),
        });
        this.addChrome(this.overviewGroup);

        this.screenShieldGroup = new St.Widget({
            name: 'screenShieldGroup',
            visible: false,
            clip_to_allocation: true,
            layout_manager: new Clutter.BinLayout(),
            constraints: new Clutter.BindConstraint({
                source: this.uiGroup,
                coordinate: Clutter.BindCoordinate.ALL,
            }),
        });
        this.addChrome(this.screenShieldGroup);

        this.panelBox = new St.BoxLayout({
            name: 'panelBox',
            vertical: true,
        });
        this.addChrome(this.panelBox, {
            affectsStruts: true,
            trackFullscreen: true,
        });
        this.panelBox.connect('notify::allocation',
                              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.addTopChrome(this.keyboardBox);
        this._keyboardHeightNotifyId = 0;

        this.screenshotUIGroup = new St.Widget({
            name: 'screenshotUIGroup',
            layout_manager: new Clutter.BinLayout(),
        });
        this.addTopChrome(this.screenshotUIGroup);

        // 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);

        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);
        global.window_group.set_child_below_sibling(this._backgroundGroup, null);
        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.screenTransition = new ScreenTransition();
        this.uiGroup.add_child(this.screenTransition);
        this.screenTransition.add_constraint(new Clutter.BindConstraint({
            source: this.uiGroup,
            coordinate: Clutter.BindCoordinate.ALL,
        }));
    }

    // 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.screenTransition.hide();

        this._inOverview = true;
        this._updateVisibility();
    }

    hideOverview() {
        this.overviewGroup.hide();
        this.screenTransition.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) {
        const bgManager = new Background.BackgroundManager({
            container: this._backgroundGroup,
            layoutManager: this,
            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;
                backgroundActor.ease({
                    opacity: 255,
                    duration: BACKGROUND_FADE_ANIMATION_TIME,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                });
            }
        }
    }

    _waitLoaded(bgManager) {
        return new Promise(resolve => {
            const id = bgManager.connect('loaded', () => {
                bgManager.disconnect(id);
                resolve();
            });
        });
    }

    _updateBackgrounds() {
        for (let i = 0; i < this._bgManagers.length; i++)
            this._bgManagers[i].destroy();

        this._bgManagers = [];

        if (Main.sessionMode.isGreeter)
            return Promise.resolve();

        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();
        }

        return Promise.all(this._bgManagers.map(this._waitLoaded));
    }

    _updateKeyboardBox() {
        this.keyboardBox.set_position(this.keyboardMonitor.x,
                                      this.keyboardMonitor.y + this.keyboardMonitor.height);
        this.keyboardBox.set_size(this.keyboardMonitor.width, -1);
    }

    _updateBoxes() {
        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.hide();

        global.stage.insert_child_below(this._systemBackground, null);

        const constraint = new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        });
        this._systemBackground.add_constraint(constraint);

        let signalId = this._systemBackground.connect('loaded', () => {
            this._systemBackground.disconnect(signalId);

            // 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
            const id = GLib.idle_add(GLib.PRIORITY_LOW, () => {
                if (this.primaryMonitor) {
                    this._systemBackground.show();
                    global.stage.show();
                    this._prepareStartupAnimation();
                    return GLib.SOURCE_REMOVE;
                } else {
                    return GLib.SOURCE_CONTINUE;
                }
            });
            GLib.Source.set_name_by_id(id, '[gnome-shell] Startup Animation');
        });
    }

    // 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.

    async _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.keyboardBox.hide();

            let monitor = this.primaryMonitor;

            if (!Main.sessionMode.hasOverview) {
                const x = monitor.x + monitor.width / 2.0;
                const 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);

            await this._updateBackgrounds();
        }

        this.emit('startup-prepared');

        this._startupAnimation();
    }

    _startupAnimation() {
        if (Meta.is_restart())
            this._startupAnimationComplete();
        else if (Main.sessionMode.isGreeter)
            this._startupAnimationGreeter();
        else
            this._startupAnimationSession();
    }

    _startupAnimationGreeter() {
        this.panelBox.ease({
            translation_y: 0,
            duration: STARTUP_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => this._startupAnimationComplete(),
        });
    }

    _startupAnimationSession() {
        const onStopped = () => this._startupAnimationComplete();
        if (Main.sessionMode.hasOverview) {
            Main.overview.runStartupAnimation(onStopped);
        } else {
            this.uiGroup.ease({
                scale_x: 1,
                scale_y: 1,
                opacity: 255,
                duration: STARTUP_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onStopped,
            });
        }
    }

    _startupAnimationComplete() {
        this._coverPane.destroy();
        this._coverPane = null;

        this._systemBackground.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');
    }

    // 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);
    }

    // addTopChrome:
    // @actor: an actor to add to the chrome
    // @params: (optional) additional params
    //
    // Like addChrome(), but adds @actor above all windows, including popups.
    addTopChrome(actor, params) {
        this.uiGroup.add_actor(actor);
        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;
        // We can't use Params.parse here because we want to drop
        // the extra values like ancestorData.actor
        for (let prop in defaultParams) {
            if (!Object.prototype.hasOwnProperty.call(params, 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;
        actor.connectObject(
            'notify::visible', this._queueUpdateRegions.bind(this),
            'notify::allocation', this._queueUpdateRegions.bind(this),
            'destroy', this._untrackActor.bind(this), 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;

        this._trackedActors.splice(i, 1);
        actor.disconnectObject(this);

        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);
    }

    _findIndexForRect(x, y, width, height) {
        let rect = new Meta.Rectangle({
            x: Math.floor(x),
            y: Math.floor(y),
            width: Math.ceil(x + width) - Math.floor(x),
            height: Math.ceil(y + height) - Math.floor(y)
        });
        return global.display.get_monitor_index_for_rect(rect);
    }

    // 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();
        return this._findIndexForRect(x, y, w, h);
    }

    _findMonitorForIndex(index) {
        if (index >= 0 && index < this.monitors.length)
            return this.monitors[index];
        return null;
    }

    findMonitorForActor(actor) {
        return this._findMonitorForIndex(this.findIndexForActor(actor));
    }

    findMonitorForPoint(x, y) {
        return this._findMonitorForIndex(this._findIndexForRect(x, y, 1, 1));
    }

    _queueUpdateRegions() {
        if (!this._updateRegionIdle) {
            this._updateRegionIdle = Meta.later_add(Meta.LaterType.BEFORE_REDRAW,
                                                    this._updateRegions.bind(this));
        }
    }

    _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;
        }

        let rects = [], struts = [], i;
        let isPopupMenuVisible = global.top_window_group.get_children().some(isPopupMetaWindow);
        const wantsInputRegion =
            !this._startingUp &&
            !isPopupMenuVisible &&
            Main.modalCount === 0 &&
            !Meta.is_wayland_compositor();

        for (i = 0; i < this._trackedActors.length; i++) {
            let actorData = this._trackedActors[i];
            if (!(actorData.affectsInputRegion && wantsInputRegion) && !actorData.affectsStruts)
                continue;

            actorData.actor.get_allocation_box();
            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, 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 });
                struts.push(strut);
            }
        }

        if (wantsInputRegion)
            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 = GObject.registerClass(
class HotCorner extends Clutter.Actor {
    _init(layoutManager, monitor, x, y) {
        super._init();

        // 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));

        let px = 0.0;
        let py = 0.0;
        if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
            px = 1.0;
            py = 0.0;
        }

        this._ripples = new Ripples.Ripples(px, py, 'ripple-box');
        this._ripples.addTo(layoutManager.uiGroup);

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

    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.set({
                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.add_child(this._corner);
            layoutManager.addChrome(this);

            if (Clutter.get_default_text_direction() == Clutter.TextDirection.RTL) {
                this._corner.set_position(this.width - this._corner.width, 0);
                this.set_pivot_point(1.0, 0.0);
                this.translation_x = -this.width;
            } else {
                this._corner.set_position(0, 0);
            }

            this._corner.connect('enter-event',
                                 this._onCornerEntered.bind(this));
            this._corner.connect('leave-event',
                                 this._onCornerLeft.bind(this));
        }
    }

    _onDestroy() {
        this.setBarrierSize(0);
        this._pressureBarrier.destroy();
        this._pressureBarrier = null;

        this._ripples.destroy();
    }

    _toggleOverview() {
        if (this._monitor.inFullscreen && !Main.overview.visible)
            return;

        if (Main.overview.shouldToggleByCornerOrButton()) {
            Main.overview.toggle();
            if (Main.overview.animationInProgress)
                this._ripples.playAnimation(this._x, this._y);
        }
    }

    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)
            this._entered = false;
        // Consume event, otherwise this will confuse onEnvironsLeft
        return Clutter.EVENT_STOP;
    }

    vfunc_leave_event(crossingEvent) {
        if (crossingEvent.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);

var ScreenTransition = GObject.registerClass(
class ScreenTransition extends Clutter.Actor {
    _init() {
        super._init({ visible: false });
    }

    vfunc_hide() {
        this.content = null;
        super.vfunc_hide();
    }

    run() {
        if (this.visible)
            return;

        Main.uiGroup.set_child_above_sibling(this, null);

        const rect = new imports.gi.cairo.RectangleInt({
            x: 0,
            y: 0,
            width: global.screen_width,
            height: global.screen_height,
        });
        const [, , , scale] = global.stage.get_capture_final_size(rect);
        this.content = global.stage.paint_to_content(rect, scale, Clutter.PaintFlag.NO_CURSORS);

        this.opacity = 255;
        this.show();

        this.ease({
            opacity: 0,
            duration: SCREEN_TRANSITION_DURATION,
            delay: SCREEN_TRANSITION_DELAY,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onStopped: () => this.hide(),
        });
    }
});
(uuay)loginManager.js� // -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported canLock, getLoginManager, registerSessionWithGDM */

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;
    }
}


async function registerSessionWithGDM() {
    log("Registering session with GDM");
    try {
        await Gio.DBus.system.call(
            'org.gnome.DisplayManager',
            '/org/gnome/DisplayManager/Manager',
            'org.gnome.DisplayManager.Manager',
            'RegisterSession',
            GLib.Variant.new('(a{sv})', [{}]), null,
            Gio.DBusCallFlags.NONE, -1, null);
    } catch (e) {
        if (!e.matches(Gio.DBusError, Gio.DBusError.UNKNOWN_METHOD))
            log(`Error registering session with GDM: ${e.message}`);
        else
            log('Not calling RegisterSession(): method not exported, GDM too old?');
    }
}

let _loginManager = null;

/**
 * getLoginManager:
 * An abstraction over systemd/logind and ConsoleKit.
 * @returns {object} - the LoginManager singleton
 *
 */
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 ([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);
            }
        });
    }

    canRebootToBootLoaderMenu(asyncCallback) {
        this._proxy.CanRebootToBootLoaderMenuRemote((result, error) => {
            if (error) {
                asyncCallback(false, false);
            } else {
                const needsAuth = result[0] === 'challenge';
                const canRebootToBootLoaderMenu = needsAuth || result[0] === 'yes';
                asyncCallback(canRebootToBootLoaderMenu, needsAuth);
            }
        });
    }

    setRebootToBootLoaderMenu() {
        /* Parameter is timeout in usec, show to menu for 60 seconds */
        this._proxy.SetRebootToBootLoaderMenuRemote(60000000);
    }

    listSessions(asyncCallback) {
        this._proxy.ListSessionsRemote((result, error) => {
            if (error)
                asyncCallback([]);
            else
                asyncCallback(result[0]);
        });
    }

    suspend() {
        this._proxy.SuspendRemote(true);
    }

    async inhibit(reason, cancellable) {
        const inVariant = new GLib.Variant('(ssss)',
            ['sleep', 'GNOME Shell', reason, 'delay']);
        const [outVariant_, fdList] =
            await this._proxy.call_with_unix_fd_list('Inhibit',
                inVariant, 0, -1, null, cancellable);
        const [fd] = fdList.steal_fds();
        return new Gio.UnixInputStream({ fd });
    }

    _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);
    }

    canRebootToBootLoaderMenu(asyncCallback) {
        asyncCallback(false, false);
    }

    setRebootToBootLoaderMenu() {
    }

    listSessions(asyncCallback) {
        asyncCallback([]);
    }

    suspend() {
        this.emit('prepare-for-sleep', true);
        this.emit('prepare-for-sleep', false);
    }

    /* eslint-disable-next-line require-await */
    async inhibit() {
        return null;
    }
};
Signals.addSignalMethods(LoginManagerDummy.prototype);
(uuay)autorunManager.js�(// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */

const { Clutter, Gio, GObject, 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.includes('/.');
}

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}`);
    }

    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}`);
        }

        if (contentTypes.length) {
            this._emitCallback(mount, contentTypes);
        } else {
            let root = mount.get_root();

            let hotplugSniffer = new HotplugSniffer();
            hotplugSniffer.SniffURIRemote(root.get_uri(),
                result => {
                    [contentTypes] = result;
                    this._emitCallback(mount, contentTypes);
                });
        }
    }

    _emitCallback(mount, 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._volumeMonitor.connectObject(
            'mount-added', this._onMountAdded.bind(this),
            'mount-removed', this._onMountRemoved.bind(this), this);
    }

    disable() {
        this._volumeMonitor.disconnectObject(this);
    }

    _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((m, 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.includes(contentType))
            return AutorunSetting.RUN;

        let ignore = this._settings.get_strv(SETTING_IGNORE);
        if (ignore.includes(contentType))
            return AutorunSetting.IGNORE;

        let openFiles = this._settings.get_strv(SETTING_OPEN_FOLDER);
        if (openFiles.includes(contentType))
            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 = GObject.registerClass(
class AutorunSource extends MessageTray.Source {
    _init(manager, mount, apps) {
        super._init(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.showNotification(this._notification);
    }

    getIcon() {
        return this.mount.get_icon();
    }

    _createPolicy() {
        return new MessageTray.NotificationApplicationPolicy('org.gnome.Nautilus');
    }
});

var AutorunNotification = GObject.registerClass(
class AutorunNotification extends MessageTray.Notification {
    _init(manager, source) {
        super._init(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({
            x_expand: true,
            x_align: Clutter.ActorAlign.START,
        });
        const icon = new St.Icon({
            gicon: app.get_icon(),
            style_class: 'hotplug-notification-item-icon',
        });
        box.add(icon);

        let label = new St.Bin({
            child: new St.Label({
                text: _("Open with %s").format(app.get_name()),
                y_align: Clutter.ActorAlign.CENTER,
            }),
        });
        box.add(label);

        const button = new St.Button({
            child: box,
            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)dash.jse~// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Dash */

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

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 Overview = imports.ui.overview;

var DASH_ANIMATION_TIME = 200;
var DASH_ITEM_LABEL_SHOW_TIME = 150;
var DASH_ITEM_LABEL_HIDE_TIME = 100;
var DASH_ITEM_HOVER_TIMEOUT = 300;

function getAppFromSource(source) {
    if (source instanceof AppDisplay.AppIcon)
        return source.app;
    else
        return null;
}

var DashIcon = GObject.registerClass(
class DashIcon extends AppDisplay.AppIcon {
    _init(app) {
        super._init(app, {
            setSizeManually: true,
            showLabel: false,
        });
    }

    popupMenu() {
        super.popupMenu(St.Side.BOTTOM);
    }

    // Disable scale-n-fade methods used during DND by parent
    scaleAndFade() {
    }

    undoScaleAndFade() {
    }

    handleDragOver() {
        return DND.DragMotionResult.CONTINUE;
    }

    acceptDrop() {
        return false;
    }
});

// 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 Graphene.Point({ x: .5, y: .5 }),
            layout_manager: new Clutter.BinLayout(),
            scale_x: 0,
            scale_y: 0,
            opacity: 0,
            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.animatingOut = false;

        this.connect('notify::scale-x', () => this.queue_relayout());
        this.connect('notify::scale-y', () => this.queue_relayout());

        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();

        const itemWidth = this.allocation.get_width();

        const labelWidth = this.label.get_width();
        const xOffset = Math.floor((itemWidth - labelWidth) / 2);
        const x = Math.clamp(stageX + xOffset, 0, global.stage.width - labelWidth);

        let node = this.label.get_theme_node();
        const yOffset = node.get_length('-y-offset');

        const y = stageY - this.label.height - yOffset;

        this.label.set_position(x, y);
        this.label.ease({
            opacity: 255,
            duration: DASH_ITEM_LABEL_SHOW_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    setLabelText(text) {
        this._labelText = text;
        this.child.accessible_name = text;
    }

    hideLabel() {
        this.label.ease({
            opacity: 0,
            duration: DASH_ITEM_LABEL_HIDE_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this.label.hide(),
        });
    }

    setChild(actor) {
        if (this.child == actor)
            return;

        this.destroy_all_children();

        this.child = actor;
        this.child.y_expand = true;
        this.add_actor(this.child);
    }

    show(animate) {
        if (this.child == null)
            return;

        let time = animate ? DASH_ANIMATION_TIME : 0;
        this.ease({
            scale_x: 1,
            scale_y: 1,
            opacity: 255,
            duration: time,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    animateOutAndDestroy() {
        this.label.hide();

        if (this.child == null) {
            this.destroy();
            return;
        }

        this.animatingOut = true;
        this.ease({
            scale_x: 0,
            scale_y: 0,
            opacity: 0,
            duration: DASH_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this.destroy(),
        });
    }
});

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.icon.y_align = Clutter.ActorAlign.CENTER;

        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(imports.misc.desktop.is('ubuntu') ? _('Remove from Favorites') : _('Unpin'));
        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' }));
    }
});

const DashIconsLayout = GObject.registerClass(
class DashIconsLayout extends Clutter.BoxLayout {
    _init() {
        super._init({
            orientation: Clutter.Orientation.HORIZONTAL,
        });
    }

    vfunc_get_preferred_width(container, forHeight) {
        const [, natWidth] = super.vfunc_get_preferred_width(container, forHeight);
        return [0, natWidth];
    }
});

const baseIconSizes = [16, 22, 24, 32, 48, 64];

var Dash = GObject.registerClass({
    Signals: { 'icon-size-changed': {} },
}, class Dash extends St.Widget {
    _init() {
        this._maxWidth = -1;
        this._maxHeight = -1;
        this.iconSize = 64;
        this._shownInitially = false;

        this._separator = null;
        this._dragPlaceholder = null;
        this._dragPlaceholderPos = -1;
        this._animatingPlaceholdersCount = 0;
        this._showLabelTimeoutId = 0;
        this._resetHoverTimeoutId = 0;
        this._labelShowing = false;

        super._init({
            name: 'dash',
            offscreen_redirect: Clutter.OffscreenRedirect.ALWAYS,
            layout_manager: new Clutter.BinLayout(),
        });

        this._dashContainer = new St.BoxLayout({
            x_align: Clutter.ActorAlign.CENTER,
            y_expand: true,
        });

        this._box = new St.Widget({
            clip_to_allocation: true,
            layout_manager: new DashIconsLayout(),
            y_expand: true,
        });
        this._box._delegate = this;

        this._dashContainer.add_child(this._box);

        this._showAppsIcon = new ShowAppsIcon();
        this._showAppsIcon.show(false);
        this._showAppsIcon.icon.setIconSize(this.iconSize);
        this._hookUpLabel(this._showAppsIcon);
        this._dashContainer.add_child(this._showAppsIcon);

        this.showAppsButton = this._showAppsIcon.toggleButton;

        this._background = new St.Widget({
            style_class: 'dash-background',
        });

        const sizerBox = new Clutter.Actor();
        sizerBox.add_constraint(new Clutter.BindConstraint({
            source: this._showAppsIcon.icon,
            coordinate: Clutter.BindCoordinate.HEIGHT,
        }));
        sizerBox.add_constraint(new Clutter.BindConstraint({
            source: this._dashContainer,
            coordinate: Clutter.BindCoordinate.WIDTH,
        }));
        this._background.add_child(sizerBox);

        this.add_child(this._background);
        this.add_child(this._dashContainer);

        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._onItemDragBegin.bind(this));
        Main.overview.connect('item-drag-end',
            this._onItemDragEnd.bind(this));
        Main.overview.connect('item-drag-cancelled',
            this._onItemDragCancelled.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));

        // Translators: this is the name of the dock/favorites area on
        // the left of the overview
        Main.ctrlAltTabManager.addGroup(this, _("Dash"), 'user-bookmarks-symbolic');
    }

    _onItemDragBegin() {
        this._dragCancelled = false;
        this._dragMonitor = {
            dragMotion: this._onItemDragMotion.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);
        }
    }

    _onItemDragCancelled() {
        this._dragCancelled = true;
        this._endItemDrag();
    }

    _onItemDragEnd() {
        if (this._dragCancelled)
            return;

        this._endItemDrag();
    }

    _endItemDrag() {
        this._clearDragPlaceholder();
        this._clearEmptyDropTarget();
        this._showAppsIcon.setDragApp(null);
        DND.removeDragMonitor(this._dragMonitor);
    }

    _onItemDragMotion(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;
    }

    _onWindowDragBegin() {
        this.ease({
            opacity: 128,
            duration: Overview.ANIMATION_TIME / 2,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

    _onWindowDragEnd() {
        this.ease({
            opacity: 255,
            duration: Overview.ANIMATION_TIME / 2,
            mode: Clutter.AnimationMode.EASE_IN_QUAD,
        });
    }

    _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);
        });

        item.child.connect('clicked', () => {
            this._labelShowing = false;
            item.hideLabel();
        });

        Main.overview.connectObject('hiding', () => {
            this._labelShowing = false;
            item.hideLabel();
        }, item.child);

        if (appIcon) {
            appIcon.connect('sync-tooltip', () => {
                this._syncLabel(item, appIcon);
            });
        }
    }

    _createAppItem(app) {
        let appIcon = new DashIcon(app);

        appIcon.connect('menu-state-changed',
                        (o, opened) => {
                            this._itemMenuStateChanged(item, opened);
                        });

        let item = new DashItemContainer();
        item.setChild(appIcon);

        // Override default AppIcon label_actor, now the
        // accessible_name is set at DashItemContainer.setLabelText
        appIcon.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) {
                GLib.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 = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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) {
                    GLib.source_remove(this._resetHoverTimeoutId);
                    this._resetHoverTimeoutId = 0;
                }
            }
        } else {
            if (this._showLabelTimeoutId > 0)
                GLib.source_remove(this._showLabelTimeoutId);
            this._showLabelTimeoutId = 0;
            item.hideLabel();
            if (this._labelShowing) {
                this._resetHoverTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 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._maxWidth === -1 || this._maxHeight === -1)
            return;

        const themeNode = this.get_theme_node();
        const maxAllocation = new Clutter.ActorBox({
            x1: 0,
            y1: 0,
            x2: this._maxWidth,
            y2: 42, /* whatever */
        });
        let maxContent = themeNode.get_content_box(maxAllocation);
        let availWidth = maxContent.x2 - maxContent.x1;
        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();
        const [, , iconWidth, iconHeight] = firstIcon.icon.get_preferred_size();
        const [, , buttonWidth, buttonHeight] = firstButton.get_preferred_size();

        // Subtract icon padding and box spacing from the available width
        availWidth -= iconChildren.length * (buttonWidth - iconWidth) +
                       (iconChildren.length - 1) * spacing;

        let availHeight = this._maxHeight;
        availHeight -= this.margin_top + this.margin_bottom;
        availHeight -= this._background.get_theme_node().get_vertical_padding();
        availHeight -= themeNode.get_vertical_padding();
        availHeight -= buttonHeight - iconHeight;

        const maxIconSize = Math.min(availWidth / iconChildren.length, availHeight);

        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] <= maxIconSize)
                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);

            icon.icon.ease({
                width: targetWidth,
                height: targetHeight,
                duration: DASH_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        }

        if (this._separator) {
            this._separator.ease({
                height: this.iconSize,
                duration: DASH_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        }
    }

    _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.includes(oldApp)) {
                removedActors.push(children[oldIndex]);
                oldIndex++;
                continue;
            }

            // App added at newIndex
            if (newApp && !oldApps.includes(newApp)) {
                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);

        // Update separator
        const nFavorites = Object.keys(favorites).length;
        const nIcons = children.length + addedItems.length - removedActors.length;
        if (nFavorites > 0 && nFavorites < nIcons) {
            if (!this._separator) {
                this._separator = new St.Widget({
                    style_class: 'dash-separator',
                    y_align: Clutter.ActorAlign.CENTER,
                    height: this.iconSize,
                });
                this._box.add_child(this._separator);
            }
            let pos = nFavorites + this._animatingPlaceholdersCount;
            if (this._dragPlaceholder)
                pos++;
            this._box.set_child_at_index(this._separator, pos);
        } else if (this._separator) {
            this._separator.destroy();
            this._separator = null;
        }

        // 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.connect('destroy', () => {
                this._animatingPlaceholdersCount--;
            });
            this._dragPlaceholder.animateOutAndDestroy();
            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 boxWidth = this._box.width;

        // 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) {
            boxWidth -= this._dragPlaceholder.width;
            numChildren--;
        }

        // Same with the separator
        if (this._separator) {
            boxWidth -= this._separator.width;
            numChildren--;
        }

        let pos;
        if (this._emptyDropTarget)
            pos = 0; // always insert at the start when dash is empty
        else if (this.text_direction === Clutter.TextDirection.RTL)
            pos = numChildren - Math.floor(x * numChildren / boxWidth);
        else
            pos = Math.floor(x * numChildren / boxWidth);

        // Put the placeholder after the last favorite if we are not
        // in the favorites zone
        if (pos > numFavorites)
            pos = numFavorites;

        if (pos !== this._dragPlaceholderPos && 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);
        }

        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 want 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;
    }

    setMaxSize(maxWidth, maxHeight) {
        if (this._maxWidth === maxWidth &&
            this._maxHeight === maxHeight)
            return;

        this._maxWidth = maxWidth;
        this._maxHeight = maxHeight;
        this._queueRedisplay();
    }
});
(uuay)fileUtils.jsA// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported collectFromDatadirs, recursivelyDeleteDir,
            recursivelyMoveDir, loadInterfaceXML, loadSubInterfaceXML */

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 recursivelyDeleteDir(dir, deleteParent) {
    let children = dir.enumerate_children('standard::name,standard::type',
        Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);

    let info;
    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)
            child.delete(null);
        else if (type == Gio.FileType.DIRECTORY)
            recursivelyDeleteDir(child, true);
    }

    if (deleteParent)
        dir.delete(null);
}

function recursivelyMoveDir(srcDir, destDir) {
    let children = srcDir.enumerate_children('standard::name,standard::type',
        Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);

    if (!destDir.query_exists(null))
        destDir.make_directory_with_parents(null);

    let info;
    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 ensureIfaceResource() {
    if (_ifaceResource)
        return;

    // 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();
}

function loadInterfaceXML(iface) {
    ensureIfaceResource();

    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);
        return new TextDecoder().decode(bytes);
    } catch (e) {
        log(`Failed to load D-Bus interface ${iface}`);
    }

    return null;
}

function loadSubInterfaceXML(iface, ifaceFile) {
    let xml = loadInterfaceXML(ifaceFile);
    if (!xml)
        return null;

    let ifaceStartTag = `<interface name="${iface}">`;
    let ifaceStopTag = '</interface>';
    let ifaceStartIndex = xml.indexOf(ifaceStartTag);
    let ifaceEndIndex = xml.indexOf(ifaceStopTag, ifaceStartIndex + 1) + ifaceStopTag.length;

    let xmlHeader = '<!DOCTYPE node PUBLIC\n' +
        '\'-//freedesktop//DTD D-BUS Object Introspection 1.0//EN\'\n' +
        '\'http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd\'>\n' +
        '<node>\n';
    let xmlFooter = '</node>';

    return (
        xmlHeader +
        xml.substr(ifaceStartIndex, ifaceEndIndex - ifaceStartIndex) +
        xmlFooter);
}
(uuay)automountManager.js�$// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Component */

const { Gio, GLib } = imports.gi;
const Params = imports.misc.params;

const GnomeSession = imports.misc.gnomeSession;
const Main = imports.ui.main;
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._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._volumeMonitor.connectObject(
            'volume-added', this._onVolumeAdded.bind(this),
            'volume-removed', this._onVolumeRemoved.bind(this),
            'drive-connected', this._onDriveConnected.bind(this),
            'drive-disconnected', this._onDriveDisconnected.bind(this),
            'drive-eject-button', this._onDriveEjectButton.bind(this), this);

        this._mountAllId = GLib.idle_add(GLib.PRIORITY_DEFAULT, this._startupMountAll.bind(this));
        GLib.Source.set_name_by_id(this._mountAllId, '[gnome-shell] this._startupMountAll');
    }

    disable() {
        this._volumeMonitor.disconnectObject(this);

        if (this._mountAllId > 0) {
            GLib.source_remove(this._mountAllId);
            this._mountAllId = 0;
        }
    }

    _InhibitorsChanged(_object, _senderName, [_inhibitor]) {
        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,
                (o, 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,
                (o, 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);

        const mountOp = 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)
                // libblockdev wrong password opening LUKS device
                e.message.includes('Failed to activate device: Incorrect passphrase') ||
                // cryptsetup returns EINVAL in many cases, including wrong TCRYPT password/parameters
                e.message.includes('Failed to load device\'s parameters: Invalid argument')) {
                this._reaskPassword(volume);
            } else {
                if (e.message.includes('Compiled against a version of libcryptsetup that does not support the VeraCrypt PIM setting')) {
                    Main.notifyError(_("Unable to unlock volume"),
                        _("The installed udisks version does not support the PIM setting"));
                }

                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) {
            GLib.source_remove(volume._allowAutorunExpireId);
            delete volume._allowAutorunExpireId;
        }
    }

    _reaskPassword(volume) {
        let prevOperation = this._activeOperations.get(volume);
        const existingDialog = prevOperation?.borrowDialog();
        let operation =
            new ShellMountOperation.ShellMountOperation(volume,
                                                        { 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 = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 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)extensionUtils.js!// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ExtensionState, ExtensionType, getCurrentExtension,
   getSettings, initTranslations, gettext, ngettext, pgettext,
   openPrefs, isOutOfDate, installImporter, serializeExtension,
   deserializeExtension */

// Common utils for the extension system and the extension
// preferences tool

const { Gio, GLib } = imports.gi;

const Gettext = imports.gettext;

const Config = imports.misc.config;

var ExtensionType = {
    SYSTEM: 1,
    PER_USER: 2,
};

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 {?object} - 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].includes('/gnome-shell/extensions/')) {
            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:
 * @param {string=} domain - 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 ||= 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);

    Object.assign(extension, Gettext.domain(domain));
}

/**
 * gettext:
 * @param {string} str - the string to translate
 *
 * Translate @str using the extension's gettext domain
 *
 * @returns {string} - the translated string
 *
 */
function gettext(str) {
    return callExtensionGettextFunc('gettext', str);
}

/**
 * ngettext:
 * @param {string} str - the string to translate
 * @param {string} strPlural - the plural form of the string
 * @param {number} n - the quantity for which translation is needed
 *
 * Translate @str and choose plural form using the extension's
 * gettext domain
 *
 * @returns {string} - the translated string
 *
 */
function ngettext(str, strPlural, n) {
    return callExtensionGettextFunc('ngettext', str, strPlural, n);
}

/**
 * pgettext:
 * @param {string} context - context to disambiguate @str
 * @param {string} str - the string to translate
 *
 * Translate @str in the context of @context using the extension's
 * gettext domain
 *
 * @returns {string} - the translated string
 *
 */
function pgettext(context, str) {
    return callExtensionGettextFunc('pgettext', context, str);
}

function callExtensionGettextFunc(func, ...args) {
    const extension = getCurrentExtension();

    if (!extension)
        throw new Error(`${func}() can only be called from extensions`);

    if (!extension[func])
        throw new Error(`${func}() is used without calling initTranslations() first`);

    return extension[func](...args);
}

/**
 * getSettings:
 * @param {string=} schema - the GSettings schema id
 * @returns {Gio.Settings} - a new settings object for @schema
 *
 * 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 ||= 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 });
}

/**
 * openPrefs:
 *
 * Open the preference dialog of the current extension
 */
function openPrefs() {
    const extension = getCurrentExtension();

    if (!extension)
        throw new Error('openPrefs() can only be called from extensions');

    try {
        const extensionManager = imports.ui.main.extensionManager;
        extensionManager.openExtensionPrefs(extension.uuid, '', {});
    } catch (e) {
        if (e.name === 'ImportError')
            throw new Error('openPrefs() cannot be called from preferences');
        logError(e, 'Failed to open extension preferences');
    }
}

function isOutOfDate(extension) {
    const [major] = Config.PACKAGE_VERSION.split('.');
    return !extension.metadata['shell-version'].some(v => v.startsWith(major));
}

function serializeExtension(extension) {
    let obj = { ...extension.metadata };

    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)slider.js0/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
/* exported Slider */

const { Atk, Clutter, GObject } = imports.gi;

const BarLevel = imports.ui.barLevel;

var SLIDER_SCROLL_STEP = 0.02; /* Slider scrolling step in % */

var Slider = GObject.registerClass({
    Signals: {
        'drag-begin': {},
        'drag-end': {},
    },
}, class Slider extends BarLevel.BarLevel {
    _init(value) {
        super._init({
            value,
            style_class: 'slider',
            can_focus: true,
            reactive: true,
            accessible_role: Atk.Role.SLIDER,
            x_expand: true,
        });

        this._releaseId = 0;
        this._dragging = false;

        this._customAccessible.connect('get-minimum-increment', this._getMinimumIncrement.bind(this));
    }

    vfunc_repaint() {
        super.vfunc_repaint();

        // Add handle
        let cr = this.get_context();
        let themeNode = this.get_theme_node();
        let [width, height] = this.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 ceiledHandleRadius = Math.ceil(handleRadius + handleBorderWidth);
        const handleX = ceiledHandleRadius +
            (width - 2 * ceiledHandleRadius) * this._value / this._maxValue;
        const 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();
    }

    vfunc_button_press_event() {
        return this.startDragging(Clutter.get_current_event());
    }

    startDragging(event) {
        if (this._dragging)
            return Clutter.EVENT_PROPAGATE;

        this._dragging = true;

        let device = event.get_device();
        let sequence = event.get_event_sequence();

        this._grab = global.stage.grab(this);

        this._grabbedDevice = device;
        this._grabbedSequence = sequence;

        // We need to emit 'drag-begin' before moving the handle to make
        // sure that no 'notify::value' 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.disconnect(this._releaseId);
                this._releaseId = 0;
            }

            if (this._grab) {
                this._grab.dismiss();
                this._grab = null;
            }

            this._grabbedSequence = null;
            this._grabbedDevice = null;
            this._dragging = false;

            this.emit('drag-end');
        }
        return Clutter.EVENT_STOP;
    }

    vfunc_button_release_event() {
        if (this._dragging && !this._grabbedSequence)
            return this._endDragging();

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_touch_event() {
        let event = Clutter.get_current_event();
        let sequence = event.get_event_sequence();

        if (!this._dragging &&
            event.type() == Clutter.EventType.TOUCH_BEGIN) {
            this.startDragging(event);
            return Clutter.EVENT_STOP;
        } else if (this._grabbedSequence &&
                   sequence.get_slot() === this._grabbedSequence.get_slot()) {
            if (event.type() == Clutter.EventType.TOUCH_UPDATE)
                return this._motionEvent(this, 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 [, 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);

        return Clutter.EVENT_STOP;
    }

    vfunc_scroll_event() {
        return this.scroll(Clutter.get_current_event());
    }

    vfunc_motion_event() {
        if (this._dragging && !this._grabbedSequence)
            return this._motionEvent(this, Clutter.get_current_event());

        return Clutter.EVENT_PROPAGATE;
    }

    _motionEvent(actor, event) {
        let absX, absY;
        [absX, absY] = event.get_coords();
        this._moveHandle(absX, absY);
        return Clutter.EVENT_STOP;
    }

    vfunc_key_press_event(keyPressEvent) {
        let key = keyPressEvent.keyval;
        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));
            return Clutter.EVENT_STOP;
        }
        return super.vfunc_key_press_event(keyPressEvent);
    }

    _moveHandle(absX, _absY) {
        let relX, sliderX;
        [sliderX] = this.get_transformed_position();
        relX = absX - sliderX;

        let width = this._barLevelWidth;
        let handleRadius = this.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;
    }

    _getMinimumIncrement() {
        return 0.1;
    }
});
(uuay)power.js?// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Clutter, Gio, GObject, 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 = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        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.add_child(this._percentageLabel);
        this.add_style_class_name('power-status');

        this._proxy = new PowerManagerProxy(Gio.DBus.system, BUS_NAME, OBJECT_PATH,
            (proxy, error) => {
                if (error) {
                    log(error.message);
                } else {
                    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.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.hide();
            this._indicator.icon_name = 'system-shutdown-symbolic';
            this._percentageLabel.hide();
            return;
        }

        // The icons
        let chargingState = this._proxy.State === UPower.DeviceState.CHARGING
            ? '-charging' : '';
        let fillLevel = 10 * Math.floor(this._proxy.Percentage / 10);
        const charged =
            this._proxy.State === UPower.DeviceState.FULLY_CHARGED ||
            (this._proxy.State === UPower.DeviceState.CHARGING && fillLevel === 100);
        const icon = charged
            ? 'battery-level-100-charged-symbolic'
            : `battery-level-${fillLevel}${chargingState}-symbolic`;

        // Make sure we fall back to fallback-icon-name and not GThemedIcon's
        // default fallbacks
        let gicon = new Gio.ThemedIcon({
            name: icon,
            use_default_fallbacks: false,
        });

        this._indicator.gicon = gicon;
        this._item.icon.gicon = gicon;

        let fallbackIcon = this._proxy.IconName;
        this._indicator.fallback_icon_name = fallbackIcon;
        this._item.icon.fallback_icon_name = fallbackIcon;

        // The icon label
        const label = _('%d\u2009%%').format(this._proxy.Percentage);
        this._percentageLabel.text = label;

        // The status label
        this._item.label.text = this._getStatus();
    }
});
(uuay)systemActions.jsxK/* exported getDefault */
const { 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 Screenshot = imports.ui.screenshot;

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 POWER_OFF_ACTION_ID        = 'power-off';
const RESTART_ACTION_ID          = 'restart';
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 SCREENSHOT_UI_ACTION_ID    = 'open-screenshot-ui';

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-restart': GObject.ParamSpec.boolean(
            'can-restart', 'can-restart', 'can-restart',
            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;

        function tokenizeKeywords(keywords) {
            return keywords.split(';').map(keyword => GLib.str_tokenize_and_fold(keyword, null)).flat(2);
        }

        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: tokenizeKeywords(_('power off;shutdown;halt;stop')),
            available: false,
        });
        this._actions.set(RESTART_ACTION_ID, {
            // Translators: The name of the restart action in search
            name: C_('search-result', 'Restart'),
            iconName: 'system-reboot-symbolic',
            // Translators: A list of keywords that match the restart action, separated by semicolons
            keywords: tokenizeKeywords(_('reboot;restart;')),
            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: tokenizeKeywords(_('lock screen')),
            available: false,
        });
        this._actions.set(LOGOUT_ACTION_ID, {
            // Translators: The name of the logout action in search
            name: C_("search-result", "Log Out"),
            iconName: 'system-log-out-symbolic',
            // Translators: A list of keywords that match the logout action, separated by semicolons
            keywords: tokenizeKeywords(_('logout;log out;sign off')),
            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: tokenizeKeywords(_('suspend;sleep')),
            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: tokenizeKeywords(_('switch user')),
            available: false,
        });
        this._actions.set(LOCK_ORIENTATION_ACTION_ID, {
            name: '',
            iconName: '',
            // Translators: A list of keywords that match the lock orientation action, separated by semicolons
            keywords: tokenizeKeywords(_('lock orientation;unlock orientation;screen;rotation')),
            available: false,
        });
        this._actions.set(SCREENSHOT_UI_ACTION_ID, {
            // Translators: The name of the screenshot UI action in search
            name: C_('search-result', 'Take a Screenshot'),
            iconName: 'record-screen-symbolic',
            // Translators: A list of keywords that match the screenshot UI action, separated by semicolons
            keywords: tokenizeKeywords(_('screenshot;screencast;snip;capture;record')),
            available: true,
        });

        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._updateOrientationLockStatus();
        });
        Main.layoutManager.connect('monitors-changed',
                                   () => this._updateOrientationLock());
        this._monitorManager.connect('notify::panel-orientation-managed',
            () => this._updateOrientationLock());
        this._updateOrientationLock();
        this._updateOrientationLockStatus();

        Main.sessionMode.connect('updated', () => this._sessionUpdated());
        this._sessionUpdated();
    }

    get canPowerOff() {
        return this._actions.get(POWER_OFF_ACTION_ID).available;
    }

    get canRestart() {
        return this._actions.get(RESTART_ACTION_ID).available;
    }

    get canSuspend() {
        return this._actions.get(SUSPEND_ACTION_ID).available;
    }

    get canLockScreen() {
        return this._actions.get(LOCK_SCREEN_ACTION_ID).available;
    }

    get canSwitchUser() {
        return this._actions.get(SWITCH_USER_ACTION_ID).available;
    }

    get canLogout() {
        return this._actions.get(LOGOUT_ACTION_ID).available;
    }

    get canLockOrientation() {
        return this._actions.get(LOCK_ORIENTATION_ACTION_ID).available;
    }

    get orientationLockIcon() {
        return this._actions.get(LOCK_ORIENTATION_ACTION_ID).iconName;
    }

    _lightdmLoginSession() {
        try {
            let seat = GLib.getenv("XDG_SEAT_PATH");
            let result = Gio.DBus.system.call_sync('org.freedesktop.DisplayManager',
                                                   seat,
                                                   'org.freedesktop.DisplayManager.Seat',
                                                   'SwitchToGreeter', null, null,
                                                   Gio.DBusCallFlags.NONE,
                                                   -1, null);
            return result;
        } catch(e) {
            return false;
        }
    }

    _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() {
        const available = this._monitorManager.get_panel_orientation_managed();

        this._actions.get(LOCK_ORIENTATION_ACTION_ID).available = available;

        this.notify('can-lock-orientation');
    }

    _updateOrientationLockStatus() {
        let locked = this._orientationSettings.get_boolean('orientation-lock');
        let action = this._actions.get(LOCK_ORIENTATION_ACTION_ID);

        // Translators: The name of the lock orientation action in search
        // and in the system status menu
        let name = locked
            ? C_('search-result', 'Unlock Screen Rotation')
            : C_('search-result', 'Lock Screen Rotation');
        let iconName = locked
            ? 'rotation-locked-symbolic'
            : 'rotation-allowed-symbolic';

        action.name = name;
        action.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 => GLib.str_tokenize_and_fold(term, null)[0]).flat(2);

        // tokenizing may return an empty array
        if (terms.length === 0)
            return [];

        let results = [];

        for (let [key, { available, keywords }] of this._actions) {
            if (available && terms.every(t => keywords.some(k => k.startsWith(t))))
                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 RESTART_ACTION_ID:
            this.activateRestart();
            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;
        case SCREENSHOT_UI_ACTION_ID:
            this.activateScreenshotUI();
            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;
        this.notify('can-lock-screen');
    }

    _updateHaveShutdown() {
        this._session.CanShutdownRemote((result, error) => {
            this._canHavePowerOff = error ? false : 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');

        this._actions.get(RESTART_ACTION_ID).available = this._canHavePowerOff && !disabled;
        this.notify('can-restart');
    }

    _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!');

        if (Main.screenShield)
            Main.screenShield.lock(true);
        else
            this._lightdmLoginSession();
    }

    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;
            });
        } else
            this._lightdmLoginSession();
    }

    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);
    }

    activateRestart() {
        if (!this._actions.get(RESTART_ACTION_ID).available)
            throw new Error('The restart action is not available!');

        this._session.RebootRemote();
    }

    activateSuspend() {
        if (!this._actions.get(SUSPEND_ACTION_ID).available)
            throw new Error('The suspend action is not available!');

        this._loginManager.suspend();
    }

    activateScreenshotUI() {
        if (!this._actions.get(SCREENSHOT_UI_ACTION_ID).available)
            throw new Error('The screenshot UI action is not available!');

        if (this._overviewHiddenId)
            return;

        this._overviewHiddenId = Main.overview.connect('hidden', () => {
            Main.overview.disconnect(this._overviewHiddenId);
            delete this._overviewHiddenId;
            Screenshot.showScreenshotUI();
        });
    }
});
(uuay)dwellClick.js?/* exported DwellClickIndicator */
const { Clutter, Gio, GLib, GObject, St } = imports.gi;

const PanelMenu = imports.ui.panelMenu;

const MOUSE_A11Y_SCHEMA       = 'org.gnome.desktop.a11y.mouse';
const KEY_DWELL_CLICK_ENABLED = 'dwell-click-enabled';
const KEY_DWELL_MODE          = 'dwell-mode';
const DWELL_MODE_WINDOW       = 'window';
const DWELL_CLICK_MODES = {
    primary: {
        name: _("Single Click"),
        icon: 'pointer-primary-click-symbolic',
        type: Clutter.PointerA11yDwellClickType.PRIMARY,
    },
    double: {
        name: _("Double Click"),
        icon: 'pointer-double-click-symbolic',
        type: Clutter.PointerA11yDwellClickType.DOUBLE,
    },
    drag: {
        name: _("Drag"),
        icon: 'pointer-drag-symbolic',
        type: Clutter.PointerA11yDwellClickType.DRAG,
    },
    secondary: {
        name: _("Secondary Click"),
        icon: 'pointer-secondary-click-symbolic',
        type: Clutter.PointerA11yDwellClickType.SECONDARY,
    },
};

var DwellClickIndicator = GObject.registerClass(
class DwellClickIndicator extends PanelMenu.Button {
    _init() {
        super._init(0.5, _("Dwell Click"));

        this._icon = new St.Icon({
            style_class: 'system-status-icon',
            icon_name: 'pointer-primary-click-symbolic',
        });
        this.add_child(this._icon);

        this._a11ySettings = new Gio.Settings({ schema_id: MOUSE_A11Y_SCHEMA });
        this._a11ySettings.connect(`changed::${KEY_DWELL_CLICK_ENABLED}`, this._syncMenuVisibility.bind(this));
        this._a11ySettings.connect(`changed::${KEY_DWELL_MODE}`, this._syncMenuVisibility.bind(this));

        this._seat = Clutter.get_default_backend().get_default_seat();
        this._seat.connect('ptr-a11y-dwell-click-type-changed', this._updateClickType.bind(this));

        this._addDwellAction(DWELL_CLICK_MODES.primary);
        this._addDwellAction(DWELL_CLICK_MODES.double);
        this._addDwellAction(DWELL_CLICK_MODES.drag);
        this._addDwellAction(DWELL_CLICK_MODES.secondary);

        this._setClickType(DWELL_CLICK_MODES.primary);
        this._syncMenuVisibility();
    }

    _syncMenuVisibility() {
        this.visible =
          this._a11ySettings.get_boolean(KEY_DWELL_CLICK_ENABLED) &&
           this._a11ySettings.get_string(KEY_DWELL_MODE) == DWELL_MODE_WINDOW;

        return GLib.SOURCE_REMOVE;
    }

    _addDwellAction(mode) {
        this.menu.addAction(mode.name, this._setClickType.bind(this, mode), mode.icon);
    }

    _updateClickType(manager, clickType) {
        for (let mode in DWELL_CLICK_MODES) {
            if (DWELL_CLICK_MODES[mode].type == clickType)
                this._icon.icon_name = DWELL_CLICK_MODES[mode].icon;
        }
    }

    _setClickType(mode) {
        this._seat.set_pointer_a11y_dwell_click_type(mode.type);
        this._icon.icon_name = mode.icon;
    }
});
(uuay)desktop.jsf// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const GLib = imports.gi.GLib;

// current desktop doesn't change unless we restart the shell or control
// the env variable. It's safe to cache matching result
const _currentDesktopsMatches = new Map();

// is:
// @name: desktop string you want to assert if it matches the current desktop env
//
// The function examples XDG_CURRENT_DESKTOP and return if the current desktop
// is part of that desktop string.
//
// Return value: if the environment isn't set or doesn't match, return False
// otherwise, return True.
function is(name) {
    if (!_currentDesktopsMatches.size) {
        const desktopsEnv = GLib.getenv('XDG_CURRENT_DESKTOP');
        if (!desktopsEnv) {
            _currentDesktopsMatches.set(name, false);
            return false;
        }

        const desktops = desktopsEnv.split(':');
        desktops.forEach(d => _currentDesktopsMatches.set(d, true));

        if (!_currentDesktopsMatches.size)
            _currentDesktopsMatches.set(name, _currentDesktopsMatches.has(name));
    }

    return !!_currentDesktopsMatches.get(name);
}
(uuay)params.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported parse */

// 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) {
    if (!allowExtras) {
        for (let prop in params) {
            if (!(prop in defaults))
                throw new Error(`Unrecognized parameter "${prop}"`);
        }
    }

    let defaultsCopy = Object.assign({}, defaults);
    return Object.assign(defaultsCopy, params);
}
(uuay)screenshot.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported ScreenshotService, ScreenshotUI, showScreenshotUI, captureScreenshot */

const { Clutter, Cogl, Gio, GObject, GLib, Graphene, Gtk, Meta, Shell, St } = imports.gi;

const Config = imports.misc.config;
const GrabHelper = imports.ui.grabHelper;
const Layout = imports.ui.layout;
const Lightbox = imports.ui.lightbox;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;
const Workspace = imports.ui.workspace;

Gio._promisify(Shell.Screenshot.prototype, 'pick_color');
Gio._promisify(Shell.Screenshot.prototype, 'screenshot');
Gio._promisify(Shell.Screenshot.prototype, 'screenshot_window');
Gio._promisify(Shell.Screenshot.prototype, 'screenshot_area');
Gio._promisify(Shell.Screenshot.prototype, 'screenshot_stage_to_content');
Gio._promisify(Shell.Screenshot, 'composite_to_stream');

const { loadInterfaceXML } = imports.misc.fileUtils;
const { DBusSenderChecker } = imports.misc.util;

const ScreenshotIface = loadInterfaceXML('org.gnome.Shell.Screenshot');

const ScreencastIface = loadInterfaceXML('org.gnome.Shell.Screencast');
const ScreencastProxy = Gio.DBusProxy.makeProxyWrapper(ScreencastIface);

var IconLabelButton = GObject.registerClass(
class IconLabelButton extends St.Button {
    _init(iconName, label, params) {
        super._init(params);

        this._container = new St.BoxLayout({
            vertical: true,
            style_class: 'icon-label-button-container',
        });
        this.set_child(this._container);

        this._container.add_child(new St.Icon({ icon_name: iconName }));
        this._container.add_child(new St.Label({
            text: label,
            x_align: Clutter.ActorAlign.CENTER,
        }));
    }
});

var Tooltip = GObject.registerClass(
class Tooltip extends St.Label {
    _init(widget, params) {
        super._init(params);

        this._widget = widget;
        this._timeoutId = null;

        this._widget.connect('notify::hover', () => {
            if (this._widget.hover)
                this.open();
            else
                this.close();
        });
    }

    open() {
        if (this._timeoutId)
            return;

        this._timeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 300, () => {
            this.opacity = 0;
            this.show();

            const extents = this._widget.get_transformed_extents();

            const xOffset = Math.floor((extents.get_width() - this.width) / 2);
            const x =
                Math.clamp(extents.get_x() + xOffset, 0, global.stage.width - this.width);

            const node = this.get_theme_node();
            const yOffset = node.get_length('-y-offset');

            const y = extents.get_y() - this.height - yOffset;

            this.set_position(x, y);
            this.ease({
                opacity: 255,
                duration: 150,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });

            this._timeoutId = null;
            return GLib.SOURCE_REMOVE;
        });
        GLib.Source.set_name_by_id(this._timeoutId, '[gnome-shell] tooltip.open');
    }

    close() {
        if (this._timeoutId) {
            GLib.source_remove(this._timeoutId);
            this._timeoutId = null;
            return;
        }

        if (!this.visible)
            return;

        this.remove_all_transitions();
        this.ease({
            opacity: 0,
            duration: 100,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this.hide(),
        });
    }
});

var UIAreaIndicator = GObject.registerClass(
class UIAreaIndicator extends St.Widget {
    _init(params) {
        super._init(params);

        this._topRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
        this._topRect.add_constraint(new Clutter.BindConstraint({
            source: this,
            coordinate: Clutter.BindCoordinate.WIDTH,
        }));
        this._topRect.add_constraint(new Clutter.SnapConstraint({
            source: this,
            from_edge: Clutter.SnapEdge.TOP,
            to_edge: Clutter.SnapEdge.TOP,
        }));
        this._topRect.add_constraint(new Clutter.SnapConstraint({
            source: this,
            from_edge: Clutter.SnapEdge.LEFT,
            to_edge: Clutter.SnapEdge.LEFT,
        }));
        this.add_child(this._topRect);

        this._bottomRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
        this._bottomRect.add_constraint(new Clutter.BindConstraint({
            source: this,
            coordinate: Clutter.BindCoordinate.WIDTH,
        }));
        this._bottomRect.add_constraint(new Clutter.SnapConstraint({
            source: this,
            from_edge: Clutter.SnapEdge.BOTTOM,
            to_edge: Clutter.SnapEdge.BOTTOM,
        }));
        this._bottomRect.add_constraint(new Clutter.SnapConstraint({
            source: this,
            from_edge: Clutter.SnapEdge.LEFT,
            to_edge: Clutter.SnapEdge.LEFT,
        }));
        this.add_child(this._bottomRect);

        this._leftRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
        this._leftRect.add_constraint(new Clutter.SnapConstraint({
            source: this,
            from_edge: Clutter.SnapEdge.LEFT,
            to_edge: Clutter.SnapEdge.LEFT,
        }));
        this._leftRect.add_constraint(new Clutter.SnapConstraint({
            source: this._topRect,
            from_edge: Clutter.SnapEdge.TOP,
            to_edge: Clutter.SnapEdge.BOTTOM,
        }));
        this._leftRect.add_constraint(new Clutter.SnapConstraint({
            source: this._bottomRect,
            from_edge: Clutter.SnapEdge.BOTTOM,
            to_edge: Clutter.SnapEdge.TOP,
        }));
        this.add_child(this._leftRect);

        this._rightRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-shade' });
        this._rightRect.add_constraint(new Clutter.SnapConstraint({
            source: this,
            from_edge: Clutter.SnapEdge.RIGHT,
            to_edge: Clutter.SnapEdge.RIGHT,
        }));
        this._rightRect.add_constraint(new Clutter.SnapConstraint({
            source: this._topRect,
            from_edge: Clutter.SnapEdge.TOP,
            to_edge: Clutter.SnapEdge.BOTTOM,
        }));
        this._rightRect.add_constraint(new Clutter.SnapConstraint({
            source: this._bottomRect,
            from_edge: Clutter.SnapEdge.BOTTOM,
            to_edge: Clutter.SnapEdge.TOP,
        }));
        this.add_child(this._rightRect);

        this._selectionRect = new St.Widget({ style_class: 'screenshot-ui-area-indicator-selection' });
        this.add_child(this._selectionRect);

        this._topRect.add_constraint(new Clutter.SnapConstraint({
            source: this._selectionRect,
            from_edge: Clutter.SnapEdge.BOTTOM,
            to_edge: Clutter.SnapEdge.TOP,
        }));

        this._bottomRect.add_constraint(new Clutter.SnapConstraint({
            source: this._selectionRect,
            from_edge: Clutter.SnapEdge.TOP,
            to_edge: Clutter.SnapEdge.BOTTOM,
        }));

        this._leftRect.add_constraint(new Clutter.SnapConstraint({
            source: this._selectionRect,
            from_edge: Clutter.SnapEdge.RIGHT,
            to_edge: Clutter.SnapEdge.LEFT,
        }));

        this._rightRect.add_constraint(new Clutter.SnapConstraint({
            source: this._selectionRect,
            from_edge: Clutter.SnapEdge.LEFT,
            to_edge: Clutter.SnapEdge.RIGHT,
        }));
    }

    setSelectionRect(x, y, width, height) {
        this._selectionRect.set_position(x, y);
        this._selectionRect.set_size(width, height);
    }
});

var UIAreaSelector = GObject.registerClass({
    Signals: { 'drag-started': {}, 'drag-ended': {} },
}, class UIAreaSelector extends St.Widget {
    _init(params) {
        super._init(params);

        // During a drag, this can be Clutter.BUTTON_PRIMARY,
        // Clutter.BUTTON_SECONDARY or the string "touch" to identify the source
        // of the drag operation.
        this._dragButton = 0;
        this._dragSequence = null;

        this._areaIndicator = new UIAreaIndicator();
        this._areaIndicator.add_constraint(new Clutter.BindConstraint({
            source: this,
            coordinate: Clutter.BindCoordinate.ALL,
        }));
        this.add_child(this._areaIndicator);

        this._topLeftHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
        this.add_child(this._topLeftHandle);
        this._topRightHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
        this.add_child(this._topRightHandle);
        this._bottomLeftHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
        this.add_child(this._bottomLeftHandle);
        this._bottomRightHandle = new St.Widget({ style_class: 'screenshot-ui-area-selector-handle' });
        this.add_child(this._bottomRightHandle);

        // This will be updated before the first drawn frame.
        this._handleSize = 0;
        this._topLeftHandle.connect('style-changed', widget => {
            this._handleSize = widget.get_theme_node().get_width();
            this._updateSelectionRect();
        });

        this.connect('notify::mapped', () => {
            if (this.mapped) {
                const [x, y] = global.get_pointer();
                this._updateCursor(x, y);
            }
        });

        // Initialize area to out of bounds so reset() below resets it.
        this._startX = -1;
        this._startY = 0;
        this._lastX = 0;
        this._lastY = 0;

        this.reset();
    }

    reset() {
        this.stopDrag();
        global.display.set_cursor(Meta.Cursor.DEFAULT);

        // Preserve area selection if possible. If the area goes out of bounds,
        // the monitors might have changed, so reset the area.
        const [x, y, w, h] = this.getGeometry();
        if (x < 0 || y < 0 || x + w > this.width || y + h > this.height) {
            // Initialize area to out of bounds so if there's no monitor,
            // the area will be reset once a monitor does appear.
            this._startX = -1;
            this._startY = 0;
            this._lastX = 0;
            this._lastY = 0;

            // This can happen when running headless without any monitors.
            if (Main.layoutManager.primaryIndex !== -1) {
                const monitor =
                    Main.layoutManager.monitors[Main.layoutManager.primaryIndex];

                this._startX = monitor.x + Math.floor(monitor.width * 3 / 8);
                this._startY = monitor.y + Math.floor(monitor.height * 3 / 8);
                this._lastX = monitor.x + Math.floor(monitor.width * 5 / 8) - 1;
                this._lastY = monitor.y + Math.floor(monitor.height * 5 / 8) - 1;
            }

            this._updateSelectionRect();
        }
    }

    getGeometry() {
        const leftX = Math.min(this._startX, this._lastX);
        const topY = Math.min(this._startY, this._lastY);
        const rightX = Math.max(this._startX, this._lastX);
        const bottomY = Math.max(this._startY, this._lastY);

        return [leftX, topY, rightX - leftX + 1, bottomY - topY + 1];
    }

    _updateSelectionRect() {
        const [x, y, w, h] = this.getGeometry();
        this._areaIndicator.setSelectionRect(x, y, w, h);

        const offset = this._handleSize / 2;
        this._topLeftHandle.set_position(x - offset, y - offset);
        this._topRightHandle.set_position(x + w - 1 - offset, y - offset);
        this._bottomLeftHandle.set_position(x - offset, y + h - 1 - offset);
        this._bottomRightHandle.set_position(x + w - 1 - offset, y + h - 1 - offset);
    }

    _computeCursorType(cursorX, cursorY) {
        const [leftX, topY, width, height] = this.getGeometry();
        const [rightX, bottomY] = [leftX + width - 1, topY + height - 1];
        const [x, y] = [cursorX, cursorY];

        // Check if the cursor overlaps the handles first.
        const limit = (this._handleSize / 2) ** 2;
        if ((leftX - x) ** 2 + (topY - y) ** 2 <= limit)
            return Meta.Cursor.NW_RESIZE;
        else if ((rightX - x) ** 2 + (topY - y) ** 2 <= limit)
            return Meta.Cursor.NE_RESIZE;
        else if ((leftX - x) ** 2 + (bottomY - y) ** 2 <= limit)
            return Meta.Cursor.SW_RESIZE;
        else if ((rightX - x) ** 2 + (bottomY - y) ** 2 <= limit)
            return Meta.Cursor.SE_RESIZE;

        // Now check the rest of the rectangle.
        const threshold =
            10 * St.ThemeContext.get_for_stage(global.stage).scaleFactor;

        if (leftX - x >= 0 && leftX - x <= threshold) {
            if (topY - y >= 0 && topY - y <= threshold)
                return Meta.Cursor.NW_RESIZE;
            else if (y - bottomY >= 0 && y - bottomY <= threshold)
                return Meta.Cursor.SW_RESIZE;
            else if (topY - y < 0 && y - bottomY < 0)
                return Meta.Cursor.WEST_RESIZE;
        } else if (x - rightX >= 0 && x - rightX <= threshold) {
            if (topY - y >= 0 && topY - y <= threshold)
                return Meta.Cursor.NE_RESIZE;
            else if (y - bottomY >= 0 && y - bottomY <= threshold)
                return Meta.Cursor.SE_RESIZE;
            else if (topY - y < 0 && y - bottomY < 0)
                return Meta.Cursor.EAST_RESIZE;
        } else if (leftX - x < 0 && x - rightX < 0) {
            if (topY - y >= 0 && topY - y <= threshold)
                return Meta.Cursor.NORTH_RESIZE;
            else if (y - bottomY >= 0 && y - bottomY <= threshold)
                return Meta.Cursor.SOUTH_RESIZE;
            else if (topY - y < 0 && y - bottomY < 0)
                return Meta.Cursor.MOVE_OR_RESIZE_WINDOW;
        }

        return Meta.Cursor.CROSSHAIR;
    }

    stopDrag() {
        if (!this._dragButton)
            return;

        if (this._dragGrab) {
            this._dragGrab.dismiss();
            this._dragGrab = null;
        }

        this._dragButton = 0;
        this._dragSequence = null;

        if (this._dragCursor === Meta.Cursor.CROSSHAIR &&
            this._lastX === this._startX && this._lastY === this._startY) {
            // The user clicked without dragging. Make up a larger selection
            // to reduce confusion.
            const offset =
                20 * St.ThemeContext.get_for_stage(global.stage).scaleFactor;
            this._startX -= offset;
            this._startY -= offset;
            this._lastX += offset;
            this._lastY += offset;

            // Keep the coordinates inside the stage.
            if (this._startX < 0) {
                this._lastX -= this._startX;
                this._startX = 0;
            } else if (this._lastX >= this.width) {
                this._startX -= this._lastX - this.width + 1;
                this._lastX = this.width - 1;
            }

            if (this._startY < 0) {
                this._lastY -= this._startY;
                this._startY = 0;
            } else if (this._lastY >= this.height) {
                this._startY -= this._lastY - this.height + 1;
                this._lastY = this.height - 1;
            }

            this._updateSelectionRect();
        }

        this.emit('drag-ended');
    }

    _updateCursor(x, y) {
        const cursor = this._computeCursorType(x, y);
        global.display.set_cursor(cursor);
    }

    _onPress(event, button, sequence) {
        if (this._dragButton)
            return Clutter.EVENT_PROPAGATE;

        const cursor = this._computeCursorType(event.x, event.y);

        // Clicking outside of the selection, or using the right mouse button,
        // or with Ctrl results in dragging a new selection from scratch.
        if (cursor === Meta.Cursor.CROSSHAIR ||
            button === Clutter.BUTTON_SECONDARY ||
            (event.modifier_state & Clutter.ModifierType.CONTROL_MASK)) {
            this._dragButton = button;

            this._dragCursor = Meta.Cursor.CROSSHAIR;
            global.display.set_cursor(Meta.Cursor.CROSSHAIR);

            [this._startX, this._startY] = [event.x, event.y];
            this._lastX = this._startX = Math.floor(this._startX);
            this._lastY = this._startY = Math.floor(this._startY);

            this._updateSelectionRect();
        } else {
            // This is a move or resize operation.
            this._dragButton = button;

            this._dragCursor = cursor;
            this._dragStartX = event.x;
            this._dragStartY = event.y;

            const [leftX, topY, width, height] = this.getGeometry();
            const rightX = leftX + width - 1;
            const bottomY = topY + height - 1;

            // For moving, start X and Y are the top left corner, while
            // last X and Y are the bottom right corner.
            if (cursor === Meta.Cursor.MOVE_OR_RESIZE_WINDOW) {
                this._startX = leftX;
                this._startY = topY;
                this._lastX = rightX;
                this._lastY = bottomY;
            }

            // Start X and Y are set to the stationary sides, while last X
            // and Y are set to the moving sides.
            if (cursor === Meta.Cursor.NW_RESIZE ||
                cursor === Meta.Cursor.WEST_RESIZE ||
                cursor === Meta.Cursor.SW_RESIZE) {
                this._startX = rightX;
                this._lastX = leftX;
            }
            if (cursor === Meta.Cursor.NE_RESIZE ||
                cursor === Meta.Cursor.EAST_RESIZE ||
                cursor === Meta.Cursor.SE_RESIZE) {
                this._startX = leftX;
                this._lastX = rightX;
            }
            if (cursor === Meta.Cursor.NW_RESIZE ||
                cursor === Meta.Cursor.NORTH_RESIZE ||
                cursor === Meta.Cursor.NE_RESIZE) {
                this._startY = bottomY;
                this._lastY = topY;
            }
            if (cursor === Meta.Cursor.SW_RESIZE ||
                cursor === Meta.Cursor.SOUTH_RESIZE ||
                cursor === Meta.Cursor.SE_RESIZE) {
                this._startY = topY;
                this._lastY = bottomY;
            }
        }

        if (this._dragButton) {
            this._dragGrab = global.stage.grab(this);
            this._dragSequence = sequence;

            this.emit('drag-started');

            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _onRelease(event, button, sequence) {
        if (this._dragButton !== button ||
            this._dragSequence?.get_slot() !== sequence?.get_slot())
            return Clutter.EVENT_PROPAGATE;

        this.stopDrag();

        // We might have finished creating a new selection, so we need to
        // update the cursor.
        this._updateCursor(event.x, event.y);

        return Clutter.EVENT_STOP;
    }

    _onMotion(event, sequence) {
        if (!this._dragButton) {
            this._updateCursor(event.x, event.y);
            return Clutter.EVENT_PROPAGATE;
        }

        if (sequence?.get_slot() !== this._dragSequence?.get_slot())
            return Clutter.EVENT_PROPAGATE;

        if (this._dragCursor === Meta.Cursor.CROSSHAIR) {
            [this._lastX, this._lastY] = [event.x, event.y];
            this._lastX = Math.floor(this._lastX);
            this._lastY = Math.floor(this._lastY);
        } else {
            let dx = Math.round(event.x - this._dragStartX);
            let dy = Math.round(event.y - this._dragStartY);

            if (this._dragCursor === Meta.Cursor.MOVE_OR_RESIZE_WINDOW) {
                const [,, selectionWidth, selectionHeight] = this.getGeometry();

                let newStartX = this._startX + dx;
                let newStartY = this._startY + dy;
                let newLastX = this._lastX + dx;
                let newLastY = this._lastY + dy;

                let overshootX = 0;
                let overshootY = 0;

                // Keep the size intact if we bumped into the stage edge.
                if (newStartX < 0) {
                    overshootX = 0 - newStartX;
                    newStartX = 0;
                    newLastX = newStartX + (selectionWidth - 1);
                } else if (newLastX > this.width - 1) {
                    overshootX = (this.width - 1) - newLastX;
                    newLastX = this.width - 1;
                    newStartX = newLastX - (selectionWidth - 1);
                }

                if (newStartY < 0) {
                    overshootY = 0 - newStartY;
                    newStartY = 0;
                    newLastY = newStartY + (selectionHeight - 1);
                } else if (newLastY > this.height - 1) {
                    overshootY = (this.height - 1) - newLastY;
                    newLastY = this.height - 1;
                    newStartY = newLastY - (selectionHeight - 1);
                }

                // Add the overshoot to the delta to create a "rubberbanding"
                // behavior of the pointer when dragging.
                dx += overshootX;
                dy += overshootY;

                this._startX = newStartX;
                this._startY = newStartY;
                this._lastX = newLastX;
                this._lastY = newLastY;
            } else {
                if (this._dragCursor === Meta.Cursor.WEST_RESIZE ||
                    this._dragCursor === Meta.Cursor.EAST_RESIZE)
                    dy = 0;
                if (this._dragCursor === Meta.Cursor.NORTH_RESIZE ||
                    this._dragCursor === Meta.Cursor.SOUTH_RESIZE)
                    dx = 0;

                // Make sure last X and Y are clamped between 0 and size - 1,
                // while always preserving the cursor dragging position relative
                // to the selection rectangle.
                this._lastX += dx;
                if (this._lastX >= this.width) {
                    dx -= this._lastX - this.width + 1;
                    this._lastX = this.width - 1;
                } else if (this._lastX < 0) {
                    dx -= this._lastX;
                    this._lastX = 0;
                }

                this._lastY += dy;
                if (this._lastY >= this.height) {
                    dy -= this._lastY - this.height + 1;
                    this._lastY = this.height - 1;
                } else if (this._lastY < 0) {
                    dy -= this._lastY;
                    this._lastY = 0;
                }

                // If we drag the handle past a selection side, update which
                // handles are which.
                if (this._lastX > this._startX) {
                    if (this._dragCursor === Meta.Cursor.NW_RESIZE)
                        this._dragCursor = Meta.Cursor.NE_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.SW_RESIZE)
                        this._dragCursor = Meta.Cursor.SE_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.WEST_RESIZE)
                        this._dragCursor = Meta.Cursor.EAST_RESIZE;
                } else {
                    // eslint-disable-next-line no-lonely-if
                    if (this._dragCursor === Meta.Cursor.NE_RESIZE)
                        this._dragCursor = Meta.Cursor.NW_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.SE_RESIZE)
                        this._dragCursor = Meta.Cursor.SW_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.EAST_RESIZE)
                        this._dragCursor = Meta.Cursor.WEST_RESIZE;
                }

                if (this._lastY > this._startY) {
                    if (this._dragCursor === Meta.Cursor.NW_RESIZE)
                        this._dragCursor = Meta.Cursor.SW_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.NE_RESIZE)
                        this._dragCursor = Meta.Cursor.SE_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.NORTH_RESIZE)
                        this._dragCursor = Meta.Cursor.SOUTH_RESIZE;
                } else {
                    // eslint-disable-next-line no-lonely-if
                    if (this._dragCursor === Meta.Cursor.SW_RESIZE)
                        this._dragCursor = Meta.Cursor.NW_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.SE_RESIZE)
                        this._dragCursor = Meta.Cursor.NE_RESIZE;
                    else if (this._dragCursor === Meta.Cursor.SOUTH_RESIZE)
                        this._dragCursor = Meta.Cursor.NORTH_RESIZE;
                }

                global.display.set_cursor(this._dragCursor);
            }

            this._dragStartX += dx;
            this._dragStartY += dy;
        }

        this._updateSelectionRect();

        return Clutter.EVENT_STOP;
    }

    vfunc_button_press_event(event) {
        if (event.button === Clutter.BUTTON_PRIMARY ||
            event.button === Clutter.BUTTON_SECONDARY)
            return this._onPress(event, event.button, null);

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_button_release_event(event) {
        if (event.button === Clutter.BUTTON_PRIMARY ||
            event.button === Clutter.BUTTON_SECONDARY)
            return this._onRelease(event, event.button, null);

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_motion_event(event) {
        return this._onMotion(event, null);
    }

    vfunc_touch_event(event) {
        if (event.type === Clutter.EventType.TOUCH_BEGIN)
            return this._onPress(event, 'touch', event.sequence);
        else if (event.type === Clutter.EventType.TOUCH_END)
            return this._onRelease(event, 'touch', event.sequence);
        else if (event.type === Clutter.EventType.TOUCH_UPDATE)
            return this._onMotion(event, event.sequence);

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_leave_event(event) {
        // If we're dragging and go over the panel we still get a leave event
        // for some reason, even though we have a grab. We don't want to switch
        // the cursor when we're dragging.
        if (!this._dragButton)
            global.display.set_cursor(Meta.Cursor.DEFAULT);

        return super.vfunc_leave_event(event);
    }
});

var UIWindowSelectorLayout = GObject.registerClass(
class UIWindowSelectorLayout extends Workspace.WorkspaceLayout {
    _init(monitorIndex) {
        super._init(null, monitorIndex, null);
    }

    vfunc_set_container(container) {
        this._container = container;
        this._syncWorkareaTracking();
    }

    vfunc_allocate(container, box) {
        const containerBox = container.allocation;
        const containerAllocationChanged =
            this._lastBox === null || !this._lastBox.equal(containerBox);
        this._lastBox = containerBox.copy();

        let layoutChanged = false;
        if (this._layout === null) {
            this._layout = this._createBestLayout(this._workarea);
            layoutChanged = true;
        }

        if (layoutChanged || containerAllocationChanged)
            this._windowSlots = this._getWindowSlots(box.copy());

        const childBox = new Clutter.ActorBox();

        const nSlots = this._windowSlots.length;
        for (let i = 0; i < nSlots; i++) {
            let [x, y, width, height, child] = this._windowSlots[i];

            childBox.set_origin(x, y);
            childBox.set_size(width, height);

            child.allocate(childBox);
        }
    }

    addWindow(window) {
        if (this._sortedWindows.includes(window))
            return;

        this._sortedWindows.push(window);

        this._container.add_child(window);

        this._layout = null;
        this.layout_changed();
    }

    reset() {
        for (const window of this._sortedWindows)
            window.destroy();

        this._sortedWindows = [];
        this._windowSlots = [];
        this._layout = null;
    }

    get windows() {
        return this._sortedWindows;
    }
});

var UIWindowSelectorWindow = GObject.registerClass(
class UIWindowSelectorWindow extends St.Button {
    _init(actor, params) {
        super._init(params);

        const window = actor.metaWindow;
        this._boundingBox = window.get_frame_rect();
        this._bufferRect = window.get_buffer_rect();
        this._bufferScale = actor.get_resource_scale();
        this._actor = new Clutter.Actor({
            content: actor.paint_to_content(null),
        });
        this.add_child(this._actor);

        this._border = new St.Bin({ style_class: 'screenshot-ui-window-selector-window-border' });
        this._border.connect('style-changed', () => {
            this._borderSize =
                this._border.get_theme_node().get_border_width(St.Side.TOP);
        });
        this.add_child(this._border);

        this._border.child = new St.Icon({
            icon_name: 'object-select-symbolic',
            style_class: 'screenshot-ui-window-selector-check',
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
        });

        this._cursor = null;
        this._cursorPoint = { x: 0, y: 0 };
        this._shouldShowCursor = actor.get_children().some(c => c.has_pointer);

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

    get boundingBox() {
        return this._boundingBox;
    }

    get windowCenter() {
        const boundingBox = this.boundingBox;
        return {
            x: boundingBox.x + boundingBox.width / 2,
            y: boundingBox.y + boundingBox.height / 2,
        };
    }

    chromeHeights() {
        return [0, 0];
    }

    chromeWidths() {
        return [0, 0];
    }

    overlapHeights() {
        return [0, 0];
    }

    get cursorPoint() {
        return {
            x: this._cursorPoint.x + this._boundingBox.x - this._bufferRect.x,
            y: this._cursorPoint.y + this._boundingBox.y - this._bufferRect.y,
        };
    }

    get bufferScale() {
        return this._bufferScale;
    }

    get windowContent() {
        return this._actor.content;
    }

    _onDestroy() {
        this.remove_child(this._actor);
        this._actor.destroy();
        this._actor = null;
        this.remove_child(this._border);
        this._border.destroy();
        this._border = null;

        if (this._cursor) {
            this.remove_child(this._cursor);
            this._cursor.destroy();
            this._cursor = null;
        }
    }

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

        // Border goes around the window.
        const borderBox = box.copy();
        borderBox.set_origin(0, 0);
        borderBox.x1 -= this._borderSize;
        borderBox.y1 -= this._borderSize;
        borderBox.x2 += this._borderSize;
        borderBox.y2 += this._borderSize;
        this._border.allocate(borderBox);

        // box should contain this._boundingBox worth of window. Compute
        // origin and size for the actor box to satisfy that.
        const xScale = box.get_width() / this._boundingBox.width;
        const yScale = box.get_height() / this._boundingBox.height;

        const [, windowW, windowH] = this._actor.content.get_preferred_size();

        const actorBox = new Clutter.ActorBox();
        actorBox.set_origin(
            (this._bufferRect.x - this._boundingBox.x) * xScale,
            (this._bufferRect.y - this._boundingBox.y) * yScale
        );
        actorBox.set_size(
            windowW * xScale / this._bufferScale,
            windowH * yScale / this._bufferScale
        );
        this._actor.allocate(actorBox);

        // Allocate the cursor if we have one.
        if (!this._cursor)
            return;

        let [, , w, h] = this._cursor.get_preferred_size();
        w *= this._cursorScale;
        h *= this._cursorScale;

        const cursorBox = new Clutter.ActorBox({
            x1: this._cursorPoint.x,
            y1: this._cursorPoint.y,
            x2: this._cursorPoint.x + w,
            y2: this._cursorPoint.y + h,
        });
        cursorBox.x1 *= xScale;
        cursorBox.x2 *= xScale;
        cursorBox.y1 *= yScale;
        cursorBox.y2 *= yScale;

        this._cursor.allocate(cursorBox);
    }

    addCursorTexture(content, point, scale) {
        if (!this._shouldShowCursor)
            return;

        // Add the cursor.
        this._cursor = new St.Widget({
            content,
            request_mode: Clutter.RequestMode.CONTENT_SIZE,
        });

        this._cursorPoint = {
            x: point.x - this._boundingBox.x,
            y: point.y - this._boundingBox.y,
        };
        this._cursorScale = scale;

        this.insert_child_below(this._cursor, this._border);
    }

    setCursorVisible(visible) {
        if (!this._cursor)
            return;

        this._cursor.visible = visible;
    }
});

var UIWindowSelector = GObject.registerClass(
class UIWindowSelector extends St.Widget {
    _init(monitorIndex, params) {
        super._init(params);
        super.layout_manager = new Clutter.BinLayout();

        this._monitorIndex = monitorIndex;

        this._layoutManager = new UIWindowSelectorLayout(monitorIndex);

        // Window screenshots
        this._container = new St.Widget({
            style_class: 'screenshot-ui-window-selector-window-container',
            x_expand: true,
            y_expand: true,
        });
        this._container.layout_manager = this._layoutManager;
        this.add_child(this._container);
    }

    capture() {
        for (const actor of global.get_window_actors()) {
            let window = actor.metaWindow;
            let workspaceManager = global.workspace_manager;
            let activeWorkspace = workspaceManager.get_active_workspace();
            if (window.is_override_redirect() ||
                !window.located_on_workspace(activeWorkspace) ||
                window.get_monitor() !== this._monitorIndex)
                continue;

            const widget = new UIWindowSelectorWindow(
                actor,
                {
                    style_class: 'screenshot-ui-window-selector-window',
                    reactive: true,
                    can_focus: true,
                    toggle_mode: true,
                }
            );

            widget.connect('key-focus-in', win => {
                Main.screenshotUI.grab_key_focus();
                win.checked = true;
            });

            if (window.has_focus()) {
                widget.checked = true;
                widget.toggle_mode = false;
            }

            this._layoutManager.addWindow(widget);
        }
    }

    reset() {
        this._layoutManager.reset();
    }

    windows() {
        return this._layoutManager.windows;
    }
});

const UIMode = {
    SCREENSHOT: 0,
    SCREENCAST: 1,
};

var ScreenshotUI = GObject.registerClass({
    Properties: {
        'screencast-in-progress': GObject.ParamSpec.boolean(
            'screencast-in-progress',
            'screencast-in-progress',
            'screencast-in-progress',
            GObject.ParamFlags.READABLE,
            false),
    },
}, class ScreenshotUI extends St.Widget {
    _init() {
        super._init({
            name: 'screenshot-ui',
            constraints: new Clutter.BindConstraint({
                source: global.stage,
                coordinate: Clutter.BindCoordinate.ALL,
            }),
            layout_manager: new Clutter.BinLayout(),
            opacity: 0,
            visible: false,
            reactive: true,
        });

        this._screencastInProgress = false;

        this._lockdownSettings = new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });

        Main.sessionMode.connect('updated', () => this.close(true));

        // The full-screen screenshot has a separate container so that we can
        // show it without the screenshot UI fade-in for a nicer animation.
        this._stageScreenshotContainer = new St.Widget({ visible: false });
        this._stageScreenshotContainer.add_constraint(new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        }));
        Main.layoutManager.screenshotUIGroup.add_child(
            this._stageScreenshotContainer);

        this._screencastAreaIndicator = new UIAreaIndicator({
            style_class: 'screenshot-ui-screencast-area-indicator',
            visible: false,
        });
        this._screencastAreaIndicator.add_constraint(new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        }));
        this.bind_property(
            'screencast-in-progress',
            this._screencastAreaIndicator,
            'visible',
            GObject.BindingFlags.DEFAULT);
        // Add it directly to the stage so that it's above popup menus.
        global.stage.add_child(this._screencastAreaIndicator);
        Shell.util_set_hidden_from_pick(this._screencastAreaIndicator, true);

        Main.layoutManager.screenshotUIGroup.add_child(this);

        this._stageScreenshot = new St.Widget({ style_class: 'screenshot-ui-screen-screenshot' });
        this._stageScreenshot.add_constraint(new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        }));
        this._stageScreenshotContainer.add_child(this._stageScreenshot);

        this._cursor = new St.Widget();
        this._stageScreenshotContainer.add_child(this._cursor);

        this._openingCoroutineInProgress = false;
        this._grabHelper = new GrabHelper.GrabHelper(this, {
            actionMode: Shell.ActionMode.POPUP,
        });

        this._areaSelector = new UIAreaSelector({
            style_class: 'screenshot-ui-area-selector',
            x_expand: true,
            y_expand: true,
            reactive: true,
        });
        this.add_child(this._areaSelector);

        this._primaryMonitorBin = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        this._primaryMonitorBin.add_constraint(
            new Layout.MonitorConstraint({ 'primary': true }));
        this.add_child(this._primaryMonitorBin);

        this._panel = new St.BoxLayout({
            style_class: 'screenshot-ui-panel',
            y_align: Clutter.ActorAlign.END,
            y_expand: true,
            vertical: true,
            offscreen_redirect: Clutter.OffscreenRedirect.AUTOMATIC_FOR_OPACITY,
        });
        this._primaryMonitorBin.add_child(this._panel);

        this._closeButton = new St.Button({
            style_class: 'screenshot-ui-close-button',
            child: new St.Icon({ icon_name: 'preview-close-symbolic' }),
        });
        this._closeButton.add_constraint(new Clutter.BindConstraint({
            source: this._panel,
            coordinate: Clutter.BindCoordinate.POSITION,
        }));
        this._closeButton.add_constraint(new Clutter.AlignConstraint({
            source: this._panel,
            align_axis: Clutter.AlignAxis.Y_AXIS,
            pivot_point: new Graphene.Point({ x: -1, y: 0.5 }),
            factor: 0,
        }));
        this._closeButtonXAlignConstraint = new Clutter.AlignConstraint({
            source: this._panel,
            align_axis: Clutter.AlignAxis.X_AXIS,
            pivot_point: new Graphene.Point({ x: 0.5, y: -1 }),
        });
        this._closeButton.add_constraint(this._closeButtonXAlignConstraint);
        this._closeButton.connect('clicked', () => this.close());
        this._primaryMonitorBin.add_child(this._closeButton);

        this._areaSelector.connect('drag-started', () => {
            this._panel.ease({
                opacity: 100,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
            this._closeButton.ease({
                opacity: 100,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        });
        this._areaSelector.connect('drag-ended', () => {
            this._panel.ease({
                opacity: 255,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
            this._closeButton.ease({
                opacity: 255,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        });

        this._typeButtonContainer = new St.Widget({
            style_class: 'screenshot-ui-type-button-container',
            layout_manager: new Clutter.BoxLayout({
                spacing: 12,
                homogeneous: true,
            }),
        });
        this._panel.add_child(this._typeButtonContainer);

        this._selectionButton = new IconLabelButton('screenshot-ui-area-symbolic', _('Selection'), {
            style_class: 'screenshot-ui-type-button',
            checked: true,
            x_expand: true,
        });
        this._selectionButton.connect('notify::checked',
            this._onSelectionButtonToggled.bind(this));
        this._typeButtonContainer.add_child(this._selectionButton);

        this.add_child(new Tooltip(this._selectionButton, {
            text: _('Area Selection'),
            style_class: 'screenshot-ui-tooltip',
            visible: false,
        }));

        this._screenButton = new IconLabelButton('screenshot-ui-display-symbolic', _('Screen'), {
            style_class: 'screenshot-ui-type-button',
            toggle_mode: true,
            x_expand: true,
        });
        this._screenButton.connect('notify::checked',
            this._onScreenButtonToggled.bind(this));
        this._typeButtonContainer.add_child(this._screenButton);

        this.add_child(new Tooltip(this._screenButton, {
            text: _('Screen Selection'),
            style_class: 'screenshot-ui-tooltip',
            visible: false,
        }));

        this._windowButton = new IconLabelButton('screenshot-ui-window-symbolic', _('Window'), {
            style_class: 'screenshot-ui-type-button',
            toggle_mode: true,
            x_expand: true,
        });
        this._windowButton.connect('notify::checked',
            this._onWindowButtonToggled.bind(this));
        this._typeButtonContainer.add_child(this._windowButton);

        this.add_child(new Tooltip(this._windowButton, {
            text: _('Window Selection'),
            style_class: 'screenshot-ui-tooltip',
            visible: false,
        }));

        this._bottomRowContainer = new St.Widget({ layout_manager: new Clutter.BinLayout() });
        this._panel.add_child(this._bottomRowContainer);

        this._shotCastContainer = new St.BoxLayout({
            style_class: 'screenshot-ui-shot-cast-container',
            x_align: Clutter.ActorAlign.START,
            x_expand: true,
        });
        this._bottomRowContainer.add_child(this._shotCastContainer);

        this._shotButton = new St.Button({
            style_class: 'screenshot-ui-shot-cast-button',
            checked: true,
        });
        this._shotButton.set_child(new St.Icon({ icon_name: 'camera-photo-symbolic' }));
        this._shotButton.connect('notify::checked',
            this._onShotButtonToggled.bind(this));
        this._shotCastContainer.add_child(this._shotButton);

        this._castButton = new St.Button({
            style_class: 'screenshot-ui-shot-cast-button',
            toggle_mode: true,
            visible: Config.HAVE_RECORDER,
        });
        this._castButton.set_child(new St.Icon({ icon_name: 'camera-web-symbolic' }));
        this._castButton.connect('notify::checked',
            this._onCastButtonToggled.bind(this));
        this._shotCastContainer.add_child(this._castButton);

        this._shotButton.bind_property('checked', this._castButton, 'checked',
            GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.INVERT_BOOLEAN);

        this._shotCastTooltip = new Tooltip(this._shotCastContainer, {
            text: _('Screenshot / Screencast'),
            style_class: 'screenshot-ui-tooltip',
            visible: false,
        });
        const shotCastCallback = () => {
            if (this._shotButton.hover || this._castButton.hover)
                this._shotCastTooltip.open();
            else
                this._shotCastTooltip.close();
        };
        this._shotButton.connect('notify::hover', shotCastCallback);
        this._castButton.connect('notify::hover', shotCastCallback);
        this.add_child(this._shotCastTooltip);

        this._captureButton = new St.Button({ style_class: 'screenshot-ui-capture-button' });
        this._captureButton.set_child(new St.Widget({
            style_class: 'screenshot-ui-capture-button-circle',
        }));
        this._captureButton.connect('clicked',
            this._onCaptureButtonClicked.bind(this));
        this._bottomRowContainer.add_child(this._captureButton);

        this._showPointerButtonContainer = new St.BoxLayout({
            x_align: Clutter.ActorAlign.END,
            x_expand: true,
        });
        this._bottomRowContainer.add_child(this._showPointerButtonContainer);

        this._showPointerButton = new St.Button({
            style_class: 'screenshot-ui-show-pointer-button',
            toggle_mode: true,
        });
        this._showPointerButton.set_child(new St.Icon({ icon_name: 'screenshot-ui-show-pointer-symbolic' }));
        this._showPointerButtonContainer.add_child(this._showPointerButton);

        this.add_child(new Tooltip(this._showPointerButton, {
            text: _('Show Pointer'),
            style_class: 'screenshot-ui-tooltip',
            visible: false,
        }));

        this._showPointerButton.connect('notify::checked', () => {
            const state = this._showPointerButton.checked;
            this._cursor.visible = state;

            const windows =
                this._windowSelectors.flatMap(selector => selector.windows());
            for (const window of windows)
                window.setCursorVisible(state);
        });
        this._cursor.visible = false;

        this._monitorBins = [];
        this._windowSelectors = [];
        this._rebuildMonitorBins();

        Main.layoutManager.connect('monitors-changed', () => {
            // Nope, not dealing with monitor changes.
            this.close(true);
            this._rebuildMonitorBins();
        });

        const uiModes =
            Shell.ActionMode.ALL &
            ~(Shell.ActionMode.LOCK_SCREEN |
                Shell.ActionMode.UNLOCK_SCREEN |
                Shell.ActionMode.LOGIN_SCREEN);

        Main.wm.addKeybinding(
            'show-screenshot-ui',
            new Gio.Settings({ schema_id: 'org.gnome.shell.keybindings' }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            uiModes,
            showScreenshotUI
        );

        Main.wm.addKeybinding(
            'show-screen-recording-ui',
            new Gio.Settings({ schema_id: 'org.gnome.shell.keybindings' }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            uiModes,
            showScreenRecordingUI
        );

        Main.wm.addKeybinding(
            'screenshot-window',
            new Gio.Settings({ schema_id: 'org.gnome.shell.keybindings' }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT | Meta.KeyBindingFlags.PER_WINDOW,
            uiModes,
            async (_display, window, _binding) => {
                try {
                    const actor = window.get_compositor_private();
                    const content = actor.paint_to_content(null);
                    const texture = content.get_texture();

                    await captureScreenshot(texture, null, 1, null);
                } catch (e) {
                    logError(e, 'Error capturing screenshot');
                }
            }
        );

        Main.wm.addKeybinding(
            'screenshot',
            new Gio.Settings({ schema_id: 'org.gnome.shell.keybindings' }),
            Meta.KeyBindingFlags.IGNORE_AUTOREPEAT,
            uiModes,
            async () => {
                try {
                    const shooter = new Shell.Screenshot();
                    const [content] = await shooter.screenshot_stage_to_content();
                    const texture = content.get_texture();

                    await captureScreenshot(texture, null, 1, null);
                } catch (e) {
                    logError(e, 'Error capturing screenshot');
                }
            }
        );
    }

    _refreshButtonLayout() {
        const buttonLayout = Meta.prefs_get_button_layout();

        this._closeButton.remove_style_class_name('left');
        this._closeButton.remove_style_class_name('right');

        if (buttonLayout.left_buttons.includes(Meta.ButtonFunction.CLOSE)) {
            this._closeButton.add_style_class_name('left');
            this._closeButtonXAlignConstraint.factor = 0;
        } else {
            this._closeButton.add_style_class_name('right');
            this._closeButtonXAlignConstraint.factor = 1;
        }
    }

    _rebuildMonitorBins() {
        for (const bin of this._monitorBins)
            bin.destroy();

        this._monitorBins = [];
        this._windowSelectors = [];
        this._screenSelectors = [];

        for (let i = 0; i < Main.layoutManager.monitors.length; i++) {
            const bin = new St.Widget({
                layout_manager: new Clutter.BinLayout(),
            });
            bin.add_constraint(new Layout.MonitorConstraint({ 'index': i }));
            this.insert_child_below(bin, this._primaryMonitorBin);
            this._monitorBins.push(bin);

            const windowSelector = new UIWindowSelector(i, {
                style_class: 'screenshot-ui-window-selector',
                x_expand: true,
                y_expand: true,
                visible: this._windowButton.checked,
            });
            if (i === Main.layoutManager.primaryIndex)
                windowSelector.add_style_pseudo_class('primary-monitor');

            bin.add_child(windowSelector);
            this._windowSelectors.push(windowSelector);

            const screenSelector = new St.Button({
                style_class: 'screenshot-ui-screen-selector',
                x_expand: true,
                y_expand: true,
                visible: this._screenButton.checked,
                reactive: true,
                can_focus: true,
                toggle_mode: true,
            });
            screenSelector.connect('key-focus-in', () => {
                this.grab_key_focus();
                screenSelector.checked = true;
            });
            bin.add_child(screenSelector);
            this._screenSelectors.push(screenSelector);

            screenSelector.connect('notify::checked', () => {
                if (!screenSelector.checked)
                    return;

                screenSelector.toggle_mode = false;

                for (const otherSelector of this._screenSelectors) {
                    if (screenSelector === otherSelector)
                        continue;

                    otherSelector.toggle_mode = true;
                    otherSelector.checked = false;
                }
            });
        }

        if (Main.layoutManager.primaryIndex !== -1)
            this._screenSelectors[Main.layoutManager.primaryIndex].checked = true;
    }

    async open(mode = UIMode.SCREENSHOT) {
        if (this._openingCoroutineInProgress)
            return;

        if (this._screencastInProgress)
            return;

        if (mode === UIMode.SCREENCAST && !Config.HAVE_RECORDER)
            return;

        this._castButton.checked = mode === UIMode.SCREENCAST;

        if (!this.visible) {
            // Screenshot UI is opening from completely closed state
            // (rather than opening back from in process of closing).
            for (const selector of this._windowSelectors)
                selector.capture();

            const windows =
                this._windowSelectors.flatMap(selector => selector.windows());
            for (const window of windows) {
                window.connect('notify::checked', () => {
                    if (!window.checked)
                        return;

                    window.toggle_mode = false;

                    for (const otherWindow of windows) {
                        if (window === otherWindow)
                            continue;

                        otherWindow.toggle_mode = true;
                        otherWindow.checked = false;
                    }
                });
            }

            this._windowButton.reactive =
                windows.length > 0 && !this._castButton.checked;
            if (!this._windowButton.reactive)
                this._selectionButton.checked = true;

            this._shooter = new Shell.Screenshot();

            this._openingCoroutineInProgress = true;
            try {
                const [content, scale, cursorContent, cursorPoint, cursorScale] =
                    await this._shooter.screenshot_stage_to_content();
                this._stageScreenshot.set_content(content);
                this._scale = scale;

                if (cursorContent !== null) {
                    this._cursor.set_content(cursorContent);
                    this._cursor.set_position(cursorPoint.x, cursorPoint.y);

                    let [, w, h] = cursorContent.get_preferred_size();
                    w *= cursorScale;
                    h *= cursorScale;
                    this._cursor.set_size(w, h);

                    this._cursorScale = cursorScale;

                    for (const window of windows) {
                        window.addCursorTexture(cursorContent, cursorPoint, cursorScale);
                        window.setCursorVisible(this._showPointerButton.checked);
                    }
                }

                this._stageScreenshotContainer.show();
            } catch (e) {
                log(`Error capturing screenshot: ${e.message}`);
            }
            this._openingCoroutineInProgress = false;
        }

        // Get rid of any popup menus.
        // We already have them captured on the screenshot anyway.
        //
        // This needs to happen before the grab below as closing menus will
        // pop their grabs.
        Main.layoutManager.emit('system-modal-opened');

        const { screenshotUIGroup } = Main.layoutManager;
        screenshotUIGroup.get_parent().set_child_above_sibling(
            screenshotUIGroup, null);

        const grabResult = this._grabHelper.grab({
            actor: this,
            onUngrab: () => this.close(),
        });
        if (!grabResult) {
            this.close(true);
            return;
        }

        this._refreshButtonLayout();

        this.remove_all_transitions();
        this.visible = true;
        this.ease({
            opacity: 255,
            duration: 200,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._stageScreenshotContainer.get_parent().remove_child(
                    this._stageScreenshotContainer);
                this.insert_child_at_index(this._stageScreenshotContainer, 0);
            },
        });
    }

    _finishClosing() {
        this.hide();

        this._shooter = null;

        // Switch back to screenshot mode.
        this._shotButton.checked = true;

        this._stageScreenshotContainer.get_parent().remove_child(
            this._stageScreenshotContainer);
        Main.layoutManager.screenshotUIGroup.insert_child_at_index(
            this._stageScreenshotContainer, 0);
        this._stageScreenshotContainer.hide();

        this._stageScreenshot.set_content(null);
        this._cursor.set_content(null);

        this._areaSelector.reset();
        for (const selector of this._windowSelectors)
            selector.reset();
    }

    close(instantly = false) {
        this._grabHelper.ungrab();

        if (instantly) {
            this._finishClosing();
            return;
        }

        this.remove_all_transitions();
        this.ease({
            opacity: 0,
            duration: 200,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: this._finishClosing.bind(this),
        });
    }

    _onSelectionButtonToggled() {
        if (this._selectionButton.checked) {
            this._selectionButton.toggle_mode = false;
            this._windowButton.checked = false;
            this._screenButton.checked = false;

            this._areaSelector.show();
            this._areaSelector.remove_all_transitions();
            this._areaSelector.reactive = true;
            this._areaSelector.ease({
                opacity: 255,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        } else {
            this._selectionButton.toggle_mode = true;

            this._areaSelector.stopDrag();
            global.display.set_cursor(Meta.Cursor.DEFAULT);

            this._areaSelector.remove_all_transitions();
            this._areaSelector.reactive = false;
            this._areaSelector.ease({
                opacity: 0,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => this._areaSelector.hide(),
            });
        }
    }

    _onScreenButtonToggled() {
        if (this._screenButton.checked) {
            this._screenButton.toggle_mode = false;
            this._selectionButton.checked = false;
            this._windowButton.checked = false;

            for (const selector of this._screenSelectors) {
                selector.show();
                selector.remove_all_transitions();
                selector.ease({
                    opacity: 255,
                    duration: 200,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                });
            }
        } else {
            this._screenButton.toggle_mode = true;

            for (const selector of this._screenSelectors) {
                selector.remove_all_transitions();
                selector.ease({
                    opacity: 0,
                    duration: 200,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    onComplete: () => selector.hide(),
                });
            }
        }
    }

    _onWindowButtonToggled() {
        if (this._windowButton.checked) {
            this._windowButton.toggle_mode = false;
            this._selectionButton.checked = false;
            this._screenButton.checked = false;

            for (const selector of this._windowSelectors) {
                selector.show();
                selector.remove_all_transitions();
                selector.ease({
                    opacity: 255,
                    duration: 200,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                });
            }
        } else {
            this._windowButton.toggle_mode = true;

            for (const selector of this._windowSelectors) {
                selector.remove_all_transitions();
                selector.ease({
                    opacity: 0,
                    duration: 200,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    onComplete: () => selector.hide(),
                });
            }
        }
    }

    _onShotButtonToggled() {
        if (this._shotButton.checked) {
            this._shotButton.toggle_mode = false;

            this._stageScreenshotContainer.show();
            this._stageScreenshotContainer.remove_all_transitions();
            this._stageScreenshotContainer.ease({
                opacity: 255,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        } else {
            this._shotButton.toggle_mode = true;
        }
    }

    _onCastButtonToggled() {
        if (this._castButton.checked) {
            this._castButton.toggle_mode = false;

            this._captureButton.add_style_pseudo_class('cast');

            this._stageScreenshotContainer.remove_all_transitions();
            this._stageScreenshotContainer.ease({
                opacity: 0,
                duration: 200,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => this._stageScreenshotContainer.hide(),
            });

            // Screen recording doesn't support window selection yet.
            if (this._windowButton.checked)
                this._selectionButton.checked = true;

            this._windowButton.reactive = false;
        } else {
            this._castButton.toggle_mode = true;

            this._captureButton.remove_style_pseudo_class('cast');

            const windows =
                this._windowSelectors.flatMap(selector => selector.windows());
            this._windowButton.reactive = windows.length > 0;
        }
    }

    _getSelectedGeometry(rescale) {
        let x, y, w, h;

        if (this._selectionButton.checked) {
            [x, y, w, h] = this._areaSelector.getGeometry();
        } else if (this._screenButton.checked) {
            const index =
                this._screenSelectors.findIndex(screen => screen.checked);
            const monitor = Main.layoutManager.monitors[index];

            x = monitor.x;
            y = monitor.y;
            w = monitor.width;
            h = monitor.height;
        }

        if (rescale) {
            x *= this._scale;
            y *= this._scale;
            w *= this._scale;
            h *= this._scale;
        }

        return [x, y, w, h];
    }

    _onCaptureButtonClicked() {
        if (this._shotButton.checked) {
            this._saveScreenshot();
            this.close();
        } else {
            // Screencast closes the UI on its own.
            this._startScreencast();
        }
    }

    _saveScreenshot() {
        if (this._selectionButton.checked || this._screenButton.checked) {
            const content = this._stageScreenshot.get_content();
            if (!content)
                return; // Failed to capture the screenshot for some reason.

            const texture = content.get_texture();
            const geometry = this._getSelectedGeometry(true);

            let cursorTexture = this._cursor.content?.get_texture();
            if (!this._cursor.visible)
                cursorTexture = null;

            captureScreenshot(
                texture, geometry, this._scale,
                {
                    texture: cursorTexture ?? null,
                    x: this._cursor.x * this._scale,
                    y: this._cursor.y * this._scale,
                    scale: this._cursorScale,
                }
            ).catch(e => logError(e, 'Error capturing screenshot'));
        } else if (this._windowButton.checked) {
            const window =
                this._windowSelectors.flatMap(selector => selector.windows())
                                     .find(win => win.checked);
            if (!window)
                return;

            const content = window.windowContent;
            if (!content)
                return;

            const texture = content.get_texture();

            let cursorTexture = this._cursor.content?.get_texture();
            if (!this._cursor.visible)
                cursorTexture = null;

            captureScreenshot(
                texture,
                null,
                window.bufferScale,
                {
                    texture: cursorTexture ?? null,
                    x: window.cursorPoint.x * window.bufferScale,
                    y: window.cursorPoint.y * window.bufferScale,
                    scale: this._cursorScale,
                }
            ).catch(e => logError(e, 'Error capturing screenshot'));
        }
    }

    _startScreencast() {
        if (this._windowButton.checked)
            return; // TODO

        const [x, y, w, h] = this._getSelectedGeometry(false);
        const drawCursor = this._cursor.visible;

        // Set up the screencast indicator rect.
        if (this._selectionButton.checked) {
            this._screencastAreaIndicator.setSelectionRect(
                ...this._areaSelector.getGeometry());
        } else if (this._screenButton.checked) {
            const index =
                this._screenSelectors.findIndex(screen => screen.checked);
            const monitor = Main.layoutManager.monitors[index];

            this._screencastAreaIndicator.setSelectionRect(
                monitor.x, monitor.y, monitor.width, monitor.height);
        }

        // Close instantly so the fade-out doesn't get recorded.
        this.close(true);

        // This is a bit awkward because creating a proxy synchronously hangs Shell.
        const doStartScreencast = () => {
            let method =
                this._screencastProxy.ScreencastRemote.bind(this._screencastProxy);
            if (w !== -1) {
                method = this._screencastProxy.ScreencastAreaRemote.bind(
                    this._screencastProxy, x, y, w, h);
            }

            method(
                GLib.build_filenamev([
                    /* Translators: this is the folder where recorded
                       screencasts are stored. */
                    _('Screencasts'),
                    /* Translators: this is a filename used for screencast
                     * recording, where "%d" and "%t" date and time, e.g.
                     * "Screencast from 07-17-2013 10:00:46 PM.webm" */
                    /* xgettext:no-c-format */
                    _('Screencast from %d %t.webm'),
                ]),
                { 'draw-cursor': new GLib.Variant('b', drawCursor) },
                ([success, path], error) => {
                    if (error !== null) {
                        this._setScreencastInProgress(false);
                        log(`Error starting screencast: ${error.message}`);
                        return;
                    }

                    if (!success) {
                        this._setScreencastInProgress(false);
                        log('Error starting screencast');
                        return;
                    }

                    this._screencastPath = path;
                }
            );
        };

        // Set this before calling the method as the screen recording indicator
        // will check it before the success callback fires.
        this._setScreencastInProgress(true);

        if (this._screencastProxy) {
            doStartScreencast();
        } else {
            new ScreencastProxy(
                Gio.DBus.session,
                'org.gnome.Shell.Screencast',
                '/org/gnome/Shell/Screencast',
                (object, error) => {
                    if (error !== null) {
                        log('Error connecting to the screencast service');
                        return;
                    }

                    this._screencastProxy = object;
                    doStartScreencast();
                }
            );
        }
    }

    stopScreencast() {
        if (!this._screencastInProgress)
            return;

        // Set this before calling the method as the screen recording indicator
        // will check it before the success callback fires.
        this._setScreencastInProgress(false);

        this._screencastProxy.StopScreencastRemote((success, error) => {
            if (error !== null) {
                log(`Error stopping screencast: ${error.message}`);
                return;
            }

            if (!success) {
                log('Error stopping screencast');
                return;
            }

            // Show a notification.
            const file = Gio.file_new_for_path(this._screencastPath);

            const source = new MessageTray.Source(
                // Translators: notification source name.
                _('Screenshot'),
                'screencast-recorded-symbolic'
            );
            const notification = new MessageTray.Notification(
                source,
                // Translators: notification title.
                _('Screencast recorded'),
                // Translators: notification body when a screencast was recorded.
                _('Click here to view the video.')
            );
            // Translators: button on the screencast notification.
            notification.addAction(_('Show in Files'), () => {
                const app =
                    Gio.app_info_get_default_for_type('inode/directory', false);

                if (app === null) {
                    // It may be null e.g. in a toolbox without nautilus.
                    log('Error showing in files: no default app set for inode/directory');
                    return;
                }

                app.launch([file], global.create_app_launch_context(0, -1));
            });
            notification.connect('activated', () => {
                try {
                    Gio.app_info_launch_default_for_uri(
                        file.get_uri(), global.create_app_launch_context(0, -1));
                } catch (err) {
                    logError(err, 'Error opening screencast');
                }
            });
            notification.setTransient(true);

            Main.messageTray.add(source);
            source.showNotification(notification);
        });
    }

    get screencast_in_progress() {
        if (!('_screencastInProgress' in this))
            return false;

        return this._screencastInProgress;
    }

    _setScreencastInProgress(inProgress) {
        if (this._screencastInProgress === inProgress)
            return;

        this._screencastInProgress = inProgress;
        this.notify('screencast-in-progress');
    }

    vfunc_key_press_event(event) {
        const symbol = event.keyval;
        if (symbol === Clutter.KEY_Return || symbol === Clutter.KEY_space ||
            ((event.modifier_state & Clutter.ModifierType.CONTROL_MASK) &&
             (symbol === Clutter.KEY_c || symbol === Clutter.KEY_C))) {
            this._onCaptureButtonClicked();
            return Clutter.EVENT_STOP;
        }

        if (symbol === Clutter.KEY_s || symbol === Clutter.KEY_S) {
            this._selectionButton.checked = true;
            return Clutter.EVENT_STOP;
        }

        if (symbol === Clutter.KEY_c || symbol === Clutter.KEY_C) {
            this._screenButton.checked = true;
            return Clutter.EVENT_STOP;
        }

        if (this._windowButton.reactive &&
            (symbol === Clutter.KEY_w || symbol === Clutter.KEY_W)) {
            this._windowButton.checked = true;
            return Clutter.EVENT_STOP;
        }

        if (symbol === Clutter.KEY_p || symbol === Clutter.KEY_P) {
            this._showPointerButton.checked = !this._showPointerButton.checked;
            return Clutter.EVENT_STOP;
        }

        if (symbol === Clutter.KEY_v || symbol === Clutter.KEY_V) {
            this._castButton.checked = !this._castButton.checked;
            return Clutter.EVENT_STOP;
        }

        if (symbol === Clutter.KEY_Left || symbol === Clutter.KEY_Right ||
            symbol === Clutter.KEY_Up || symbol === Clutter.KEY_Down) {
            let direction;
            if (symbol === Clutter.KEY_Left)
                direction = St.DirectionType.LEFT;
            else if (symbol === Clutter.KEY_Right)
                direction = St.DirectionType.RIGHT;
            else if (symbol === Clutter.KEY_Up)
                direction = St.DirectionType.UP;
            else if (symbol === Clutter.KEY_Down)
                direction = St.DirectionType.DOWN;

            if (this._windowButton.checked) {
                const window =
                    this._windowSelectors.flatMap(selector => selector.windows())
                        .find(win => win.checked) ?? null;
                this.navigate_focus(window, direction, false);
            } else if (this._screenButton.checked) {
                const screen =
                    this._screenSelectors.find(selector => selector.checked) ?? null;
                this.navigate_focus(screen, direction, false);
            }

            return Clutter.EVENT_STOP;
        }

        return super.vfunc_key_press_event(event);
    }
});

/**
 * Stores a PNG-encoded screenshot into the clipboard and a file, and shows a
 * notification.
 *
 * @param {GLib.Bytes} bytes - The PNG-encoded screenshot.
 * @param {GdkPixbuf.Pixbuf} pixbuf - The Pixbuf with the screenshot.
 */
function _storeScreenshot(bytes, pixbuf) {
    // Store to the clipboard first in case storing to file fails.
    const clipboard = St.Clipboard.get_default();
    clipboard.set_content(St.ClipboardType.CLIPBOARD, 'image/png', bytes);

    const time = GLib.DateTime.new_now_local();

    // This will be set in the first save to disk branch and then accessed
    // in the second save to disk branch, so we need to declare it outside.
    let file;

    // The function is declared here rather than inside the condition to
    // satisfy eslint.

    /**
     * Returns a filename suffix with an increasingly large index.
     *
     * @returns {Generator<string|*, void, *>} suffix string
     */
    function* suffixes() {
        yield '';

        for (let i = 1; ; i++)
            yield `-${i}`;
    }

    const lockdownSettings =
        new Gio.Settings({ schema_id: 'org.gnome.desktop.lockdown' });
    const disableSaveToDisk =
        lockdownSettings.get_boolean('disable-save-to-disk');

    if (!disableSaveToDisk) {
        const dir = Gio.File.new_for_path(GLib.build_filenamev([
            GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES) || GLib.get_home_dir(),
            // Translators: name of the folder under ~/Pictures for screenshots.
            _('Screenshots'),
        ]));

        try {
            dir.make_directory_with_parents(null);
        } catch (e) {
            if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
                throw e;
        }

        const timestamp = time.format('%Y-%m-%d %H-%M-%S');
        // Translators: this is the name of the file that the screenshot is
        // saved to. The placeholder is a timestamp, e.g. "2017-05-21 12-24-03".
        const name = _('Screenshot from %s').format(timestamp);

        // If the target file already exists, try appending a suffix with an
        // increasing number to it.
        for (const suffix of suffixes()) {
            file = Gio.File.new_for_path(GLib.build_filenamev([
                dir.get_path(), `${name}${suffix}.png`,
            ]));

            try {
                const stream = file.create(Gio.FileCreateFlags.NONE, null);
                stream.write_bytes(bytes, null);
                break;
            } catch (e) {
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
                    throw e;
            }
        }

        // Add it to recent files.
        Gtk.RecentManager.get_default().add_item(file.get_uri());
    }

    // Create a St.ImageContent icon for the notification. We want
    // St.ImageContent specifically because it preserves the aspect ratio when
    // shown in a notification.
    const pixels = pixbuf.read_pixel_bytes();
    const content =
        St.ImageContent.new_with_preferred_size(pixbuf.width, pixbuf.height);
    content.set_bytes(
        pixels,
        Cogl.PixelFormat.RGBA_8888,
        pixbuf.width,
        pixbuf.height,
        pixbuf.rowstride
    );

    // Show a notification.
    const source = new MessageTray.Source(
        // Translators: notification source name.
        _('Screenshot'),
        'screenshot-recorded-symbolic'
    );
    const notification = new MessageTray.Notification(
        source,
        // Translators: notification title.
        _('Screenshot captured'),
        // Translators: notification body when a screenshot was captured.
        _('You can paste the image from the clipboard.'),
        { datetime: time, gicon: content }
    );

    if (!disableSaveToDisk) {
        // Translators: button on the screenshot notification.
        notification.addAction(_('Show in Files'), () => {
            const app =
                Gio.app_info_get_default_for_type('inode/directory', false);

            if (app === null) {
                // It may be null e.g. in a toolbox without nautilus.
                log('Error showing in files: no default app set for inode/directory');
                return;
            }

            app.launch([file], global.create_app_launch_context(0, -1));
        });
        notification.connect('activated', () => {
            try {
                Gio.app_info_launch_default_for_uri(
                    file.get_uri(), global.create_app_launch_context(0, -1));
            } catch (err) {
                logError(err, 'Error opening screenshot');
            }
        });
    }

    notification.setTransient(true);
    Main.messageTray.add(source);
    source.showNotification(notification);
}

/**
 * Captures a screenshot from a texture, given a region, scale and optional
 * cursor data.
 *
 * @param {Cogl.Texture} texture - The texture to take the screenshot from.
 * @param {number[4]} [geometry] - The region to use: x, y, width and height.
 * @param {number} scale - The texture scale.
 * @param {Object} [cursor] - Cursor data to include in the screenshot.
 * @param {Cogl.Texture} cursor.texture - The cursor texture.
 * @param {number} cursor.x - The cursor x coordinate.
 * @param {number} cursor.y - The cursor y coordinate.
 * @param {number} cursor.scale - The cursor texture scale.
 */
async function captureScreenshot(texture, geometry, scale, cursor) {
    const stream = Gio.MemoryOutputStream.new_resizable();
    const [x, y, w, h] = geometry ?? [0, 0, -1, -1];
    if (cursor === null)
        cursor = { texture: null, x: 0, y: 0, scale: 1 };

    global.display.get_sound_player().play_from_theme(
        'screen-capture', _('Screenshot taken'), null);

    const pixbuf = await Shell.Screenshot.composite_to_stream(
        texture,
        x, y, w, h,
        scale,
        cursor.texture, cursor.x, cursor.y, cursor.scale,
        stream
    );

    stream.close(null);
    _storeScreenshot(stream.steal_as_bytes(), pixbuf);
}

/**
 * Shows the screenshot UI.
 */
function showScreenshotUI() {
    Main.screenshotUI.open().catch(err => {
        logError(err, 'Error opening the screenshot UI');
    });
}

/**
 * Shows the screen recording UI.
 */
function showScreenRecordingUI() {
    Main.screenshotUI.open(UIMode.SCREENCAST).catch(err => {
        logError(err, 'Error opening the screenshot UI');
    });
}

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._senderChecker = new DBusSenderChecker([
            'org.gnome.SettingsDaemon.MediaKeys',
            'org.freedesktop.impl.portal.desktop.gtk',
            'org.freedesktop.impl.portal.desktop.gnome',
            'org.gnome.Screenshot',
        ]);

        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);
    }

    async _createScreenshot(invocation, needsDisk = true, restrictCallers = 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)) {
            invocation.return_error_literal(
                Gio.IOErrorEnum, Gio.IOErrorEnum.BUSY,
                'There is an ongoing operation for this sender');
            return null;
        } else if (lockedDown) {
            invocation.return_error_literal(
                Gio.IOErrorEnum, Gio.IOErrorEnum.PERMISSION_DENIED,
                'Saving to disk is disabled');
            return null;
        } else if (restrictCallers) {
            try {
                await this._senderChecker.checkInvocation(invocation);
            } catch (e) {
                invocation.return_gerror(e);
                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;
    }

    *_resolveRelativeFilename(filename) {
        filename = filename.replace(/\.png$/, '');

        let path = [
            GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_PICTURES),
            GLib.get_home_dir(),
        ].find(p => p && GLib.file_test(p, GLib.FileTest.EXISTS));

        if (!path)
            return null;

        yield Gio.File.new_for_path(
            GLib.build_filenamev([path, `${filename}.png`]));

        for (let idx = 1; ; idx++) {
            yield Gio.File.new_for_path(
                GLib.build_filenamev([path, `${filename}-${idx}.png`]));
        }
    }

    _createStream(filename, invocation) {
        if (filename == '')
            return [Gio.MemoryOutputStream.new_resizable(), null];

        if (GLib.path_is_absolute(filename)) {
            try {
                let file = Gio.File.new_for_path(filename);
                let stream = file.replace(null, false, Gio.FileCreateFlags.NONE, null);
                return [stream, file];
            } catch (e) {
                invocation.return_gerror(e);
                this._removeShooterForSender(invocation.get_sender());
                return [null, null];
            }
        }

        let err;
        for (let file of this._resolveRelativeFilename(filename)) {
            try {
                let stream = file.create(Gio.FileCreateFlags.NONE, null);
                return [stream, file];
            } catch (e) {
                err = e;
                if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.EXISTS))
                    break;
            }
        }

        invocation.return_gerror(err);
        this._removeShooterForSender(invocation.get_sender());
        return [null, null];
    }

    _flashAsync(shooter) {
        return new Promise((resolve, _reject) => {
            shooter.connect('screenshot_taken', (s, area) => {
                const flashspot = new Flashspot(area);
                flashspot.fire(resolve);

                global.display.get_sound_player().play_from_theme(
                    'screen-capture', _('Screenshot taken'), null);
            });
        });
    }

    _onScreenshotComplete(stream, file, invocation) {
        stream.close(null);

        let filenameUsed = '';
        if (file) {
            filenameUsed = file.get_path();
        } else {
            let bytes = stream.steal_as_bytes();
            let clipboard = St.Clipboard.get_default();
            clipboard.set_content(St.ClipboardType.CLIPBOARD, 'image/png', bytes);
        }

        let retval = GLib.Variant.new('(bs)', [true, 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];
    }

    async 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 = await this._createScreenshot(invocation);
        if (!screenshot)
            return;

        let [stream, file] = this._createStream(filename, invocation);
        if (!stream)
            return;

        try {
            await Promise.all([
                flash ? this._flashAsync(screenshot) : null,
                screenshot.screenshot_area(x, y, width, height, stream),
            ]);
            this._onScreenshotComplete(stream, file, invocation);
        } catch (e) {
            invocation.return_value(new GLib.Variant('(bs)', [false, '']));
        } finally {
            this._removeShooterForSender(invocation.get_sender());
        }
    }

    async ScreenshotWindowAsync(params, invocation) {
        let [includeFrame, includeCursor, flash, filename] = params;
        let screenshot = await this._createScreenshot(invocation);
        if (!screenshot)
            return;

        let [stream, file] = this._createStream(filename, invocation);
        if (!stream)
            return;

        try {
            await Promise.all([
                flash ? this._flashAsync(screenshot) : null,
                screenshot.screenshot_window(includeFrame, includeCursor, stream),
            ]);
            this._onScreenshotComplete(stream, file, invocation);
        } catch (e) {
            invocation.return_value(new GLib.Variant('(bs)', [false, '']));
        } finally {
            this._removeShooterForSender(invocation.get_sender());
        }
    }

    async ScreenshotAsync(params, invocation) {
        let [includeCursor, flash, filename] = params;
        let screenshot = await this._createScreenshot(invocation);
        if (!screenshot)
            return;

        let [stream, file] = this._createStream(filename, invocation);
        if (!stream)
            return;

        try {
            await Promise.all([
                flash ? this._flashAsync(screenshot) : null,
                screenshot.screenshot(includeCursor, stream),
            ]);
            this._onScreenshotComplete(stream, file, invocation);
        } catch (e) {
            invocation.return_value(new GLib.Variant('(bs)', [false, '']));
        } finally {
            this._removeShooterForSender(invocation.get_sender());
        }
    }

    async SelectAreaAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        let selectArea = new SelectArea();
        try {
            let areaRectangle = await selectArea.selectAsync();
            let retRectangle = this._unscaleArea(
                areaRectangle.x, areaRectangle.y,
                areaRectangle.width, areaRectangle.height);
            invocation.return_value(GLib.Variant.new('(iiii)', retRectangle));
        } catch (e) {
            invocation.return_error_literal(
                Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
                'Operation was cancelled');
        }
    }

    async FlashAreaAsync(params, invocation) {
        try {
            await this._senderChecker.checkInvocation(invocation);
        } catch (e) {
            invocation.return_gerror(e);
            return;
        }

        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, y, width, height });
        flashspot.fire();
        invocation.return_value(null);
    }

    async PickColorAsync(params, invocation) {
        const screenshot = await this._createScreenshot(invocation, false, false);
        if (!screenshot)
            return;

        const pickPixel = new PickPixel(screenshot);
        try {
            const color = await pickPixel.pickAsync();
            const { red, green, blue } = color;
            const retval = GLib.Variant.new('(a{sv})', [{
                color: GLib.Variant.new('(ddd)', [
                    red / 255.0,
                    green / 255.0,
                    blue / 255.0,
                ]),
            }]);
            invocation.return_value(retval);
        } catch (e) {
            invocation.return_error_literal(
                Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
                'Operation was cancelled');
        } finally {
            this._removeShooterForSender(invocation.get_sender());
        }
    }
};

var SelectArea = GObject.registerClass(
class SelectArea extends St.Widget {
    _init() {
        this._startX = -1;
        this._startY = -1;
        this._lastX = 0;
        this._lastY = 0;
        this._result = null;

        super._init({
            visible: false,
            reactive: true,
            x: 0,
            y: 0,
        });
        Main.uiGroup.add_actor(this);

        this._grabHelper = new GrabHelper.GrabHelper(this);

        const constraint = new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        });
        this.add_constraint(constraint);

        this._rubberband = new St.Widget({
            style_class: 'select-area-rubberband',
            visible: false,
        });
        this.add_actor(this._rubberband);
    }

    async selectAsync() {
        global.display.set_cursor(Meta.Cursor.CROSSHAIR);
        Main.uiGroup.set_child_above_sibling(this, null);
        this.show();

        try {
            await this._grabHelper.grabAsync({ actor: this });
        } finally {
            global.display.set_cursor(Meta.Cursor.DEFAULT);

            GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                this.destroy();
                return GLib.SOURCE_REMOVE;
            });
        }

        return this._result;
    }

    _getGeometry() {
        return new Meta.Rectangle({
            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,
        });
    }

    vfunc_motion_event(motionEvent) {
        if (this._startX == -1 || this._startY == -1 || this._result)
            return Clutter.EVENT_PROPAGATE;

        [this._lastX, this._lastY] = [motionEvent.x, motionEvent.y];
        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;
    }

    vfunc_button_press_event(buttonEvent) {
        if (this._result)
            return Clutter.EVENT_PROPAGATE;

        [this._startX, this._startY] = [buttonEvent.x, buttonEvent.y];
        this._startX = Math.floor(this._startX);
        this._startY = Math.floor(this._startY);
        this._rubberband.set_position(this._startX, this._startY);

        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_button_release_event() {
        if (this._startX === -1 || this._startY === -1 || this._result)
            return Clutter.EVENT_PROPAGATE;

        this._result = this._getGeometry();
        this.ease({
            opacity: 0,
            duration: 200,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this._grabHelper.ungrab(),
        });
        return Clutter.EVENT_PROPAGATE;
    }
});

var RecolorEffect = GObject.registerClass({
    Properties: {
        color: GObject.ParamSpec.boxed(
            'color', 'color', 'replacement color',
            GObject.ParamFlags.WRITABLE,
            Clutter.Color.$gtype),
        chroma: GObject.ParamSpec.boxed(
            'chroma', 'chroma', 'color to replace',
            GObject.ParamFlags.WRITABLE,
            Clutter.Color.$gtype),
        threshold: GObject.ParamSpec.float(
            'threshold', 'threshold', 'threshold',
            GObject.ParamFlags.WRITABLE,
            0.0, 1.0, 0.0),
        smoothing: GObject.ParamSpec.float(
            'smoothing', 'smoothing', 'smoothing',
            GObject.ParamFlags.WRITABLE,
            0.0, 1.0, 0.0),
    },
}, class RecolorEffect extends Shell.GLSLEffect {
    _init(params) {
        this._color = new Clutter.Color();
        this._chroma = new Clutter.Color();
        this._threshold = 0;
        this._smoothing = 0;

        this._colorLocation = null;
        this._chromaLocation = null;
        this._thresholdLocation = null;
        this._smoothingLocation = null;

        super._init(params);

        this._colorLocation = this.get_uniform_location('recolor_color');
        this._chromaLocation = this.get_uniform_location('chroma_color');
        this._thresholdLocation = this.get_uniform_location('threshold');
        this._smoothingLocation = this.get_uniform_location('smoothing');

        this._updateColorUniform(this._colorLocation, this._color);
        this._updateColorUniform(this._chromaLocation, this._chroma);
        this._updateFloatUniform(this._thresholdLocation, this._threshold);
        this._updateFloatUniform(this._smoothingLocation, this._smoothing);
    }

    _updateColorUniform(location, color) {
        if (!location)
            return;

        this.set_uniform_float(location,
            3, [color.red / 255, color.green / 255, color.blue / 255]);
        this.queue_repaint();
    }

    _updateFloatUniform(location, value) {
        if (!location)
            return;

        this.set_uniform_float(location, 1, [value]);
        this.queue_repaint();
    }

    set color(c) {
        if (this._color.equal(c))
            return;

        this._color = c;
        this.notify('color');

        this._updateColorUniform(this._colorLocation, this._color);
    }

    set chroma(c) {
        if (this._chroma.equal(c))
            return;

        this._chroma = c;
        this.notify('chroma');

        this._updateColorUniform(this._chromaLocation, this._chroma);
    }

    set threshold(value) {
        if (this._threshold === value)
            return;

        this._threshold = value;
        this.notify('threshold');

        this._updateFloatUniform(this._thresholdLocation, this._threshold);
    }

    set smoothing(value) {
        if (this._smoothing === value)
            return;

        this._smoothing = value;
        this.notify('smoothing');

        this._updateFloatUniform(this._smoothingLocation, this._smoothing);
    }

    vfunc_build_pipeline() {
        // Conversion parameters from https://en.wikipedia.org/wiki/YCbCr
        const decl = `
            vec3 rgb2yCrCb(vec3 c) {                                \n
                float y = 0.299 * c.r + 0.587 * c.g + 0.114 * c.b;  \n
                float cr = 0.7133 * (c.r - y);                      \n
                float cb = 0.5643 * (c.b - y);                      \n
                return vec3(y, cr, cb);                             \n
            }                                                       \n
                                                                    \n
            uniform vec3 chroma_color;                              \n
            uniform vec3 recolor_color;                             \n
            uniform float threshold;                                \n
            uniform float smoothing;                                \n`;
        const src = `
            vec3 mask = rgb2yCrCb(chroma_color.rgb);                \n
            vec3 yCrCb = rgb2yCrCb(cogl_color_out.rgb);             \n
            float blend =                                           \n
              smoothstep(threshold,                                 \n
                         threshold + smoothing,                     \n
                         distance(yCrCb.gb, mask.gb));              \n
            cogl_color_out.rgb =                                    \n
              mix(recolor_color, cogl_color_out.rgb, blend);        \n`;

        this.add_glsl_snippet(Shell.SnippetHook.FRAGMENT, decl, src, false);
    }
});

var PickPixel = GObject.registerClass(
class PickPixel extends St.Widget {
    _init(screenshot) {
        super._init({ visible: false, reactive: true });

        this._screenshot = screenshot;

        this._result = null;
        this._color = null;
        this._inPick = false;

        Main.uiGroup.add_actor(this);

        this._grabHelper = new GrabHelper.GrabHelper(this);

        const constraint = new Clutter.BindConstraint({
            source: global.stage,
            coordinate: Clutter.BindCoordinate.ALL,
        });
        this.add_constraint(constraint);

        const action = new Clutter.ClickAction();
        action.connect('clicked', async () => {
            await this._pickColor(...action.get_coords());
            this._result = this._color;
            this._grabHelper.ungrab();
        });
        this.add_action(action);

        this._recolorEffect = new RecolorEffect({
            chroma: new Clutter.Color({
                red: 80,
                green: 219,
                blue: 181,
            }),
            threshold: 0.04,
            smoothing: 0.07,
        });
        this._previewCursor = new St.Icon({
            icon_name: 'color-pick',
            icon_size: Meta.prefs_get_cursor_size(),
            effect: this._recolorEffect,
            visible: false,
        });
        Main.uiGroup.add_actor(this._previewCursor);
    }

    async pickAsync() {
        global.display.set_cursor(Meta.Cursor.BLANK);
        Main.uiGroup.set_child_above_sibling(this, null);
        this.show();

        this._pickColor(...global.get_pointer());

        try {
            await this._grabHelper.grabAsync({ actor: this });
        } finally {
            global.display.set_cursor(Meta.Cursor.DEFAULT);
            this._previewCursor.destroy();

            GLib.idle_add(GLib.PRIORITY_DEFAULT, () => {
                this.destroy();
                return GLib.SOURCE_REMOVE;
            });
        }

        return this._result;
    }

    async _pickColor(x, y) {
        if (this._inPick)
            return;

        this._inPick = true;
        this._previewCursor.set_position(x, y);
        [this._color] = await this._screenshot.pick_color(x, y);
        this._inPick = false;

        if (!this._color)
            return;

        this._recolorEffect.color = this._color;
        this._previewCursor.show();
    }

    vfunc_motion_event(motionEvent) {
        const { x, y } = motionEvent;
        this._pickColor(x, y);
        return Clutter.EVENT_PROPAGATE;
    }
});

var FLASHSPOT_ANIMATION_OUT_TIME = 500; // milliseconds

var Flashspot = GObject.registerClass(
class Flashspot extends Lightbox.Lightbox {
    _init(area) {
        super._init(Main.uiGroup, {
            inhibitEvents: true,
            width: area.width,
            height: area.height,
        });
        this.style_class = 'flashspot';
        this.set_position(area.x, area.y);
    }

    fire(doneCallback) {
        this.set({ visible: true, opacity: 255 });
        this.ease({
            opacity: 0,
            duration: FLASHSPOT_ANIMATION_OUT_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                if (doneCallback)
                    doneCallback();
                this.destroy();
            },
        });
    }
});
(uuay)audioDeviceSelection.js�/* exported AudioDeviceSelectionDBus */
const { Clutter, Gio, GLib, GObject, Meta, Shell, St } = imports.gi;

const Dialog = imports.ui.dialog;
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 = GObject.registerClass({
    Signals: { 'device-selected': { param_types: [GObject.TYPE_UINT] } },
}, class AudioDeviceSelectionDialog extends ModalDialog.ModalDialog {
    _init(devices) {
        super._init({ 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');
    }

    _buildLayout() {
        let content = new Dialog.MessageDialogContent({
            title: _('Select Audio Device'),
        });

        this._selectionBox = new St.BoxLayout({
            style_class: 'audio-selection-box',
            x_align: Clutter.ActorAlign.CENTER,
            x_expand: true,
        });
        content.add_child(this._selectionBox);

        this.contentLayout.add_child(content);

        if (Main.sessionMode.allowSettings) {
            this.addButton({
                action: this._openSettings.bind(this),
                label: _('Sound Settings'),
            });
        }
        this.addButton({
            action: () => this.close(),
            label: _('Cancel'),
            key: Clutter.KEY_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) {
        const 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;
                return GLib.SOURCE_REMOVE;
            });
        });

        const icon = new St.Icon({
            style_class: 'audio-selection-device-icon',
            icon_name: this._getDeviceIcon(device),
        });
        box.add(icon);

        const label = new St.Label({
            style_class: 'audio-selection-device-label',
            text: this._getDeviceLabel(device),
            x_align: Clutter.ActorAlign.CENTER,
        });
        box.add(label);

        const 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();
        const 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)ripples.js// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Ripples */

const { Clutter, St } = imports.gi;

// Shamelessly copied from the layout "hotcorner" ripples implementation
var Ripples = class Ripples {
    constructor(px, py, styleClass) {
        this._x = 0;
        this._y = 0;

        this._px = px;
        this._py = py;

        this._ripple1 = new St.BoxLayout({
            style_class: styleClass,
            opacity: 0,
            can_focus: false,
            reactive: false,
            visible: false,
        });
        this._ripple1.set_pivot_point(px, py);

        this._ripple2 = new St.BoxLayout({
            style_class: styleClass,
            opacity: 0,
            can_focus: false,
            reactive: false,
            visible: false,
        });
        this._ripple2.set_pivot_point(px, py);

        this._ripple3 = new St.BoxLayout({
            style_class: styleClass,
            opacity: 0,
            can_focus: false,
            reactive: false,
            visible: false,
        });
        this._ripple3.set_pivot_point(px, py);
    }

    destroy() {
        this._ripple1.destroy();
        this._ripple2.destroy();
        this._ripple3.destroy();
    }

    _animRipple(ripple, delay, duration, 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 a separate
        // tween to give a non-linear curve to the fade-away and make
        // it more visible in the middle section.

        ripple.x = this._x;
        ripple.y = this._y;
        ripple.visible = true;
        ripple.opacity = 255 * Math.sqrt(startOpacity);
        ripple.scale_x = ripple.scale_y = startScale;
        ripple.set_translation(-this._px * ripple.width, -this._py * ripple.height, 0.0);

        ripple.ease({
            opacity: 0,
            delay,
            duration,
            mode: Clutter.AnimationMode.EASE_IN_QUAD,
        });
        ripple.ease({
            scale_x: finalScale,
            scale_y: finalScale,
            delay,
            duration,
            mode: Clutter.AnimationMode.LINEAR,
            onComplete: () => (ripple.visible = false),
        });
    }

    addTo(stage) {
        if (this._stage !== undefined)
            throw new Error('Ripples already added');

        this._stage = stage;
        this._stage.add_actor(this._ripple1);
        this._stage.add_actor(this._ripple2);
        this._stage.add_actor(this._ripple3);
    }

    playAnimation(x, y) {
        if (this._stage === undefined)
            throw new Error('Ripples not added');

        this._x = x;
        this._y = y;

        this._stage.set_child_above_sibling(this._ripple1, null);
        this._stage.set_child_above_sibling(this._ripple2, this._ripple1);
        this._stage.set_child_above_sibling(this._ripple3, this._ripple2);

        // 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,    830,   0.25,  1.0,     1.5);
        this._animRipple(this._ripple2,  50,   1000,   0.0,   0.7,     1.25);
        this._animRipple(this._ripple3, 350,   1000,   0.0,   0.3,     1);
    }
};
(uuay)windowAttentionHandler.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WindowAttentionHandler */

const { GObject, Shell } = imports.gi;

const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

var WindowAttentionHandler = class {
    constructor() {
        this._tracker = Shell.WindowTracker.get_default();
        global.display.connectObject(
            'window-demands-attention', this._onWindowDemandsAttention.bind(this),
            'window-marked-urgent', this._onWindowDemandsAttention.bind(this),
            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 WindowAttentionSource(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.showNotification(notification);

        window.connectObject('notify::title', () => {
            [title, banner] = this._getTitleAndBanner(app, window);
            notification.update(title, banner);
        }, source);
    }
};

var WindowAttentionSource = GObject.registerClass(
class WindowAttentionSource extends MessageTray.Source {
    _init(app, window) {
        this._window = window;
        this._app = app;

        super._init(app.get_name());

        this._window.connectObject(
            'notify::demands-attention', this._sync.bind(this),
            'notify::urgent', this._sync.bind(this),
            'focus', () => this.destroy(),
            'unmanaged', () => this.destroy(), this);
    }

    _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) {
        this._window.disconnectObject(this);

        super.destroy(params);
    }

    open() {
        Main.activateWindow(this._window);
    }
});
(uuay)rfkill.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Indicator */

const { Gio, GObject } = imports.gi;
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 = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        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();

        this._sync();
    }

    _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.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)messageList.js�[/* exported MessageListSection */
const {
    Atk, Clutter, Gio, GLib, GObject, Graphene, Meta, Pango, St,
} = imports.gi;
const Main = imports.ui.main;
const MessageTray = imports.ui.messageTray;

const Util = imports.misc.util;

var MESSAGE_ANIMATION_TIME = 100;

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 = GObject.registerClass(
class URLHighlighter extends St.Label {
    _init(text = '', lineWrap, allowMarkup) {
        super._init({
            reactive: true,
            style_class: 'url-highlighter',
            x_expand: true,
            x_align: Clutter.ActorAlign.START,
        });
        this._linkColor = '#ccccff';
        this.connect('style-changed', () => {
            let [hasColor, color] = this.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.clutter_text.line_wrap = lineWrap;
        this.clutter_text.line_wrap_mode = Pango.WrapMode.WORD_CHAR;

        this.setMarkup(text, allowMarkup);
    }

    vfunc_button_press_event(buttonEvent) {
        // 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 (!this.visible || this.get_paint_opacity() == 0)
            return Clutter.EVENT_PROPAGATE;

        // Keep Notification from seeing this and taking
        // a pointer grab, which would block our button-release-event
        // handler, if an URL is clicked
        return this._findUrlAtPos(buttonEvent) != -1;
    }

    vfunc_button_release_event(buttonEvent) {
        if (!this.visible || this.get_paint_opacity() == 0)
            return Clutter.EVENT_PROPAGATE;

        let urlId = this._findUrlAtPos(buttonEvent);
        if (urlId != -1) {
            let url = this._urls[urlId].url;
            if (!url.includes(':'))
                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;
    }

    vfunc_motion_event(motionEvent) {
        if (!this.visible || this.get_paint_opacity() == 0)
            return Clutter.EVENT_PROPAGATE;

        let urlId = this._findUrlAtPos(motionEvent);
        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;
    }

    vfunc_leave_event(crossingEvent) {
        if (!this.visible || this.get_paint_opacity() == 0)
            return Clutter.EVENT_PROPAGATE;

        if (this._cursorChanged) {
            this._cursorChanged = false;
            global.display.set_cursor(Meta.Cursor.DEFAULT);
        }
        return super.vfunc_leave_event(crossingEvent);
    }

    setMarkup(text, allowMarkup) {
        text = text ? _fixMarkup(text, allowMarkup) : '';
        this._text = text;

        this.clutter_text.set_markup(text);
        /* clutter_text.text contain text without markup */
        this._urls = Util.findUrls(this.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.clutter_text.set_markup(markup);
    }

    _findUrlAtPos(event) {
        let { x, y } = event;
        [, x, y] = this.transform_stage_point(x, y);
        let findPos = -1;
        for (let i = 0; i < this.clutter_text.text.length; i++) {
            let [, px, py, lineHeight] = this.clutter_text.position_to_coords(i);
            if (py > y || py + lineHeight < y || x < px)
                continue;
            findPos = i;
        }
        if (findPos != -1) {
            for (let i = 0; i < this._urls.length; i++) {
                if (findPos >= this._urls[i].pos &&
                    this._urls[i].pos + this._urls[i].url.length > findPos)
                    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;

        this._container?.disconnectObject(this);

        this._container = container;

        if (this._container) {
            this._container.connectObject(
                'notify::scale-x', () => this.layout_changed(),
                'notify::scale-y', () => this.layout_changed(), this);
        }
    }

    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);
            const [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) {
        for (let i = 0; i < container.get_n_children(); i++) {
            let child = container.get_child_at_index(i);

            if (child.visible)
                child.allocate(box);
        }
    }
});


var Message = GObject.registerClass({
    Signals: {
        'close': {},
        'expanded': {},
        'unexpanded': {},
    },
}, class Message extends St.Button {
    _init(title, body) {
        super._init({
            style_class: 'message',
            accessible_role: Atk.Role.NOTIFICATION,
            can_focus: true,
            x_expand: true,
            y_expand: true,
        });

        this.expanded = false;
        this._useBodyMarkup = false;

        let vbox = new St.BoxLayout({
            vertical: true,
            x_expand: true,
        });
        this.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: Clutter.ActorAlign.START,
            visible: false,
        });
        hbox.add_actor(this._iconBin);

        const 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,
        });
        titleBox.add_actor(this._secondaryBin);

        const closeIcon = new St.Icon({
            icon_name: 'window-close-symbolic',
            icon_size: 16,
        });
        this._closeButton = new St.Button({
            style_class: 'message-close-button',
            child: closeIcon, opacity: 0,
            y_align: Clutter.ActorAlign.CENTER,
        });
        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.add_style_class_name('message-body');
        this._bodyStack.add_actor(this.bodyLabel);
        this.setBody(body);

        this._closeButton.connect('clicked', this.close.bind(this));
        let actorHoverId = this.connect('notify::hover', this._sync.bind(this));
        this._closeButton.connect('destroy', this.disconnect.bind(this, actorHoverId));
        this.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 });
        const 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);
        }

        if (animate) {
            this._bodyStack.ease_property('@layout.expansion', 1, {
                progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                duration: MessageTray.ANIMATION_TIME,
            });

            this._actionBin.scale_y = 0;
            this._actionBin.ease({
                scale_y: 1,
                duration: MessageTray.ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        } else {
            this._bodyStack.layout_manager.expansion = 1;
            this._actionBin.scale_y = 1;
        }

        this.emit('expanded');
    }

    unexpand(animate) {
        if (animate) {
            this._bodyStack.ease_property('@layout.expansion', 0, {
                progress_mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                duration: MessageTray.ANIMATION_TIME,
            });

            this._actionBin.ease({
                scale_y: 0,
                duration: MessageTray.ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                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.hover && this.canClose();
        this._closeButton.opacity = visible ? 255 : 0;
        this._closeButton.reactive = visible;
    }

    _onDestroy() {
    }

    vfunc_key_press_event(keyEvent) {
        let keysym = keyEvent.keyval;

        if (keysym == Clutter.KEY_Delete ||
            keysym == Clutter.KEY_KP_Delete) {
            if (this.canClose()) {
                this.close();
                return Clutter.EVENT_STOP;
            }
        }
        return super.vfunc_key_press_event(keyEvent);
    }
});

var MessageListSection = GObject.registerClass({
    Properties: {
        'can-clear': GObject.ParamSpec.boolean(
            'can-clear', 'can-clear', 'can-clear',
            GObject.ParamFlags.READABLE,
            false),
        'empty': GObject.ParamSpec.boolean(
            'empty', 'empty', 'empty',
            GObject.ParamFlags.READABLE,
            true),
    },
    Signals: {
        'can-clear-changed': {},
        'empty-changed': {},
        'message-focused': { param_types: [Message.$gtype] },
    },
}, class MessageListSection extends St.BoxLayout {
    _init() {
        super._init({
            style_class: 'message-list-section',
            clip_to_allocation: true,
            vertical: true,
            x_expand: true,
        });

        this._list = new St.BoxLayout({
            style_class: 'message-list-section-list',
            vertical: true,
        });
        this.add_actor(this._list);

        this._list.connect('actor-added', this._sync.bind(this));
        this._list.connect('actor-removed', this._sync.bind(this));

        Main.sessionMode.connectObject(
            'updated', () => this._sync(), this);

        this._empty = true;
        this._canClear = false;
        this._sync();
    }

    get empty() {
        return this._empty;
    }

    get canClear() {
        return this._canClear;
    }

    get _messages() {
        return this._list.get_children().map(i => i.child);
    }

    _onKeyFocusIn(messageActor) {
        this.emit('message-focused', messageActor);
    }

    get allowed() {
        return true;
    }

    addMessage(message, animate) {
        this.addMessageAtIndex(message, -1, animate);
    }

    addMessageAtIndex(message, index, animate) {
        if (this._messages.includes(message))
            throw new Error('Message was already added previously');

        let listItem = new St.Bin({
            child: message,
            layout_manager: new ScaleLayout(),
            pivot_point: new Graphene.Point({ x: .5, y: .5 }),
        });
        listItem._connectionsIds = [];

        listItem._connectionsIds.push(message.connect('key-focus-in',
            this._onKeyFocusIn.bind(this)));
        listItem._connectionsIds.push(message.connect('close', () => {
            this.removeMessage(message, true);
        }));
        listItem._connectionsIds.push(message.connect('destroy', () => {
            listItem._connectionsIds.forEach(id => message.disconnect(id));
            listItem.destroy();
        }));

        this._list.insert_child_at_index(listItem, index);

        if (animate) {
            listItem.set({ scale_x: 0, scale_y: 0 });
            listItem.ease({
                scale_x: 1,
                scale_y: 1,
                duration: MESSAGE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        }
    }

    moveMessage(message, index, animate) {
        if (!this._messages.includes(message))
            throw new Error(`Impossible to move untracked message`);

        let listItem = message.get_parent();

        if (!animate) {
            this._list.set_child_at_index(listItem, index);
            return;
        }

        let onComplete = () => {
            this._list.set_child_at_index(listItem, index);
            listItem.ease({
                scale_x: 1,
                scale_y: 1,
                duration: MESSAGE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            });
        };
        listItem.ease({
            scale_x: 0,
            scale_y: 0,
            duration: MESSAGE_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete,
        });
    }

    removeMessage(message, animate) {
        const messages = this._messages;

        if (!messages.includes(message))
            throw new Error(`Impossible to remove untracked message`);

        let listItem = message.get_parent();
        listItem._connectionsIds.forEach(id => message.disconnect(id));

        let nextMessage = null;

        if (message.has_key_focus()) {
            const index = messages.indexOf(message);
            nextMessage =
                messages[index + 1] ||
                messages[index - 1] ||
                this._list;
        }

        if (animate) {
            listItem.ease({
                scale_x: 0,
                scale_y: 0,
                duration: MESSAGE_ANIMATION_TIME,
                mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                onComplete: () => {
                    listItem.destroy();
                    nextMessage?.grab_key_focus();
                },
            });
        } else {
            listItem.destroy();
            nextMessage?.grab_key_focus();
        }
    }

    clear() {
        let messages = this._messages.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];
                message.get_parent().ease({
                    translation_x: this._list.width,
                    opacity: 0,
                    duration: MESSAGE_ANIMATION_TIME,
                    delay: i * delay,
                    mode: Clutter.AnimationMode.EASE_OUT_QUAD,
                    onComplete: () => message.close(),
                });
            }
        }
    }

    _shouldShow() {
        return !this.empty;
    }

    _sync() {
        let messages = this._messages;
        let empty = messages.length == 0;

        if (this._empty != empty) {
            this._empty = empty;
            this.notify('empty');
        }

        let canClear = messages.some(m => m.canClose());
        if (this._canClear != canClear) {
            this._canClear = canClear;
            this.notify('can-clear');
        }

        this.visible = this.allowed && this._shouldShow();
    }
});
(uuay)popupMenu.jsʣ// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported PopupMenuItem, PopupSeparatorMenuItem, Switch, PopupSwitchMenuItem,
            PopupImageMenuItem, PopupMenu, PopupDummyMenu, PopupSubMenu,
            PopupMenuSection, PopupSubMenuMenuItem, PopupMenuManager */

const { Atk, Clutter, Gio, GObject, Graphene, Shell, St } = imports.gi;
const Signals = imports.signals;

const BoxPointer = imports.ui.boxpointer;
const Main = imports.ui.main;
const Params = imports.misc.params;

var Ornament = {
    NONE: 0,
    DOT: 1,
    CHECK: 2,
    HIDDEN: 3,
};

function isPopupMenuItemVisible(child) {
    if (child._delegate instanceof PopupMenuSection) {
        if (child._delegate.isEmpty())
            return false;
    }
    return child.visible;
}

/**
 * arrowIcon
 * @param {St.Side} side - Side to which the arrow points.
 * @returns {St.Icon} a new arrow icon
 */
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;
    }

    const 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 = GObject.registerClass({
    Properties: {
        'active': GObject.ParamSpec.boolean('active', 'active', 'active',
                                            GObject.ParamFlags.READWRITE,
                                            false),
        'sensitive': GObject.ParamSpec.boolean('sensitive', 'sensitive', 'sensitive',
                                               GObject.ParamFlags.READWRITE,
                                               true),
    },
    Signals: {
        'activate': { param_types: [Clutter.Event.$gtype] },
    },
}, class PopupBaseMenuItem extends St.BoxLayout {
    _init(params) {
        params = Params.parse(params, {
            reactive: true,
            activate: true,
            hover: true,
            style_class: null,
            can_focus: true,
        });
        super._init({
            style_class: 'popup-menu-item',
            reactive: params.reactive,
            track_hover: params.reactive,
            can_focus: params.can_focus,
            accessible_role: Atk.Role.MENU_ITEM,
        });
        this._delegate = this;

        this._ornament = Ornament.NONE;
        this._ornamentLabel = new St.Label({ style_class: 'popup-menu-ornament' });
        this.add(this._ornamentLabel);

        this._parent = null;
        this._active = false;
        this._activatable = params.reactive && params.activate;
        this._sensitive = true;

        if (!this._activatable)
            this.add_style_class_name('popup-inactive-menu-item');

        if (params.style_class)
            this.add_style_class_name(params.style_class);

        if (params.reactive && params.hover)
            this.bind_property('hover', this, 'active', GObject.BindingFlags.SYNC_CREATE);
    }

    get actor() {
        /* This is kept for compatibility with current implementation, and we
           don't want to warn here yet since PopupMenu depends on this */
        return this;
    }

    _getTopMenu() {
        if (this._parent)
            return this._parent._getTopMenu();
        else
            return this;
    }

    _setParent(parent) {
        this._parent = parent;
    }

    vfunc_button_press_event() {
        if (!this._activatable)
            return Clutter.EVENT_PROPAGATE;

        // This is the CSS active state
        this.add_style_pseudo_class('active');
        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_button_release_event() {
        if (!this._activatable)
            return Clutter.EVENT_PROPAGATE;

        this.remove_style_pseudo_class('active');
        this.activate(Clutter.get_current_event());
        return Clutter.EVENT_STOP;
    }

    vfunc_touch_event(touchEvent) {
        if (!this._activatable)
            return Clutter.EVENT_PROPAGATE;

        if (touchEvent.type == Clutter.EventType.TOUCH_END) {
            this.remove_style_pseudo_class('active');
            this.activate(Clutter.get_current_event());
            return Clutter.EVENT_STOP;
        } else if (touchEvent.type == Clutter.EventType.TOUCH_BEGIN) {
            // This is the CSS active state
            this.add_style_pseudo_class('active');
        }
        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_key_press_event(keyEvent) {
        if (global.focus_manager.navigate_from_event(Clutter.get_current_event()))
            return Clutter.EVENT_STOP;

        if (!this._activatable)
            return super.vfunc_key_press_event(keyEvent);

        let state = keyEvent.modifier_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 = keyEvent.keyval;
        if (symbol == Clutter.KEY_space || symbol == Clutter.KEY_Return) {
            this.activate(Clutter.get_current_event());
            return Clutter.EVENT_STOP;
        }
        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_key_focus_in() {
        super.vfunc_key_focus_in();
        this.active = true;
    }

    vfunc_key_focus_out() {
        super.vfunc_key_focus_out();
        this.active = false;
    }

    activate(event) {
        this.emit('activate', event);
    }

    get active() {
        return this._active;
    }

    set active(active) {
        let activeChanged = active != this.active;
        if (activeChanged) {
            this._active = active;
            if (active) {
                this.add_style_class_name('selected');
                if (this.can_focus)
                    this.grab_key_focus();
            } else {
                this.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 triggered,
                // so we should track it in our own, which would involve some work
                // in the container
                this.remove_style_pseudo_class('active');
            }
            this.notify('active');
        }
    }

    syncSensitive() {
        let sensitive = this.sensitive;
        this.reactive = sensitive;
        this.can_focus = sensitive;
        this.notify('sensitive');
        return sensitive;
    }

    getSensitive() {
        const parentSensitive = this._parent?.sensitive ?? true;
        return this._activatable && this._sensitive && parentSensitive;
    }

    setSensitive(sensitive) {
        if (this._sensitive == sensitive)
            return;

        this._sensitive = sensitive;
        this.syncSensitive();
    }

    get sensitive() {
        return this.getSensitive();
    }

    set sensitive(sensitive) {
        this.setSensitive(sensitive);
    }

    setOrnament(ornament) {
        if (ornament == this._ornament)
            return;

        this._ornament = ornament;

        if (ornament == Ornament.DOT) {
            this._ornamentLabel.text = '\u2022';
            this.add_accessible_state(Atk.StateType.CHECKED);
        } else if (ornament == Ornament.CHECK) {
            this._ornamentLabel.text = '\u2713';
            this.add_accessible_state(Atk.StateType.CHECKED);
        } else if (ornament == Ornament.NONE || ornament == Ornament.HIDDEN) {
            this._ornamentLabel.text = '';
            this.remove_accessible_state(Atk.StateType.CHECKED);
        }

        this._ornamentLabel.visible = ornament != Ornament.HIDDEN;
    }
});

var PopupMenuItem = GObject.registerClass(
class PopupMenuItem extends PopupBaseMenuItem {
    _init(text, params) {
        super._init(params);

        this.label = new St.Label({
            text,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this.label);
        this.label_actor = this.label;
    }
});


var PopupSeparatorMenuItem = GObject.registerClass(
class PopupSeparatorMenuItem extends PopupBaseMenuItem {
    _init(text) {
        super._init({
            style_class: 'popup-separator-menu-item',
            reactive: false,
            can_focus: false,
        });

        this.label = new St.Label({ text: text || '' });
        this.add(this.label);
        this.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-separator',
            x_expand: true,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this._separator);
    }

    _syncVisibility() {
        this.label.visible = this.label.text != '';
    }
});

var Switch = GObject.registerClass({
    Properties: {
        'state': GObject.ParamSpec.boolean(
            'state', 'state', 'state',
            GObject.ParamFlags.READWRITE,
            false),
    },
}, class Switch extends St.Bin {
    _init(state) {
        this._state = false;

        super._init({
            style_class: 'toggle-switch',
            accessible_role: Atk.Role.CHECK_BOX,
            state,
        });
    }

    get state() {
        return this._state;
    }

    set state(state) {
        if (this._state === state)
            return;

        if (state)
            this.add_style_pseudo_class('checked');
        else
            this.remove_style_pseudo_class('checked');

        this._state = state;
        this.notify('state');
    }

    toggle() {
        this.state = !this.state;
    }
});

var PopupSwitchMenuItem = GObject.registerClass({
    Signals: { 'toggled': { param_types: [GObject.TYPE_BOOLEAN] } },
}, class PopupSwitchMenuItem extends PopupBaseMenuItem {
    _init(text, active, params) {
        super._init(params);

        this.label = new St.Label({
            text,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._switch = new Switch(active);

        this.accessible_role = Atk.Role.CHECK_MENU_ITEM;
        this.checkAccessibleState();
        this.label_actor = this.label;

        this.add_child(this.label);

        this._statusBin = new St.Bin({
            x_align: Clutter.ActorAlign.END,
            x_expand: true,
        });
        this.add_child(this._statusBin);

        this._statusLabel = new St.Label({
            text: '',
            style_class: 'popup-status-menu-item',
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._statusBin.child = this._switch;
    }

    setStatus(text) {
        if (text != null) {
            this._statusLabel.text = text;
            this._statusBin.child = this._statusLabel;
            this.reactive = false;
            this.accessible_role = Atk.Role.MENU_ITEM;
        } else {
            this._statusBin.child = this._switch;
            this.reactive = true;
            this.accessible_role = Atk.Role.CHECK_MENU_ITEM;
        }
        this.checkAccessibleState();
    }

    activate(event) {
        if (this._switch.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.state = state;
        this.checkAccessibleState();
    }

    checkAccessibleState() {
        switch (this.accessible_role) {
        case Atk.Role.CHECK_MENU_ITEM:
            if (this._switch.state)
                this.add_accessible_state(Atk.StateType.CHECKED);
            else
                this.remove_accessible_state(Atk.StateType.CHECKED);
            break;
        default:
            this.remove_accessible_state(Atk.StateType.CHECKED);
        }
    }
});

var PopupImageMenuItem = GObject.registerClass(
class PopupImageMenuItem extends PopupBaseMenuItem {
    _init(text, icon, params) {
        super._init(params);

        this._icon = new St.Icon({
            style_class: 'popup-menu-icon',
            x_align: Clutter.ActorAlign.END,
        });
        this.add_child(this._icon);
        this.label = new St.Label({
            text,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this.label);
        this.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 (this.constructor === PopupMenuBase)
            throw new TypeError(`Cannot instantiate abstract class ${this.constructor.name}`);

        this.sourceActor = sourceActor;
        this.focusActor = sourceActor;
        this._parent = null;

        this.box = new St.BoxLayout({
            vertical: true,
            x_expand: true,
            y_expand: true,
        });

        if (styleClass !== undefined)
            this.box.style_class = styleClass;
        this.length = 0;

        this.isOpen = false;

        this._activeMenuItem = null;
        this._settingsActions = { };

        this._sensitive = true;

        Main.sessionMode.connectObject('updated', () => this._sessionUpdated(), this);
    }

    _getTopMenu() {
        if (this._parent)
            return this._parent._getTopMenu();
        else
            return this;
    }

    _setParent(parent) {
        this._parent = parent;
    }

    getSensitive() {
        const parentSensitive = this._parent?.sensitive ?? true;
        return this._sensitive && parentSensitive;
    }

    setSensitive(sensitive) {
        this._sensitive = sensitive;
        this.emit('notify::sensitive');
    }

    get sensitive() {
        return this.getSensitive();
    }

    set sensitive(sensitive) {
        this.setSensitive(sensitive);
    }

    _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', (o, 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.visible = Main.sessionMode.allowSettings;
        this._settingsActions[desktopFile] = menuItem;

        return menuItem;
    }

    _setSettingsVisibility(visible) {
        for (let id in this._settingsActions) {
            let item = this._settingsActions[id];
            item.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.active = false;
        this._activeMenuItem = submenuItem;
        this.emit('active-changed', submenuItem);
    }

    _connectItemSignals(menuItem) {
        menuItem.connectObject(
            'notify::active', () => {
                const { active } = menuItem;
                if (active && this._activeMenuItem !== menuItem) {
                    if (this._activeMenuItem)
                        this._activeMenuItem.active = false;
                    this._activeMenuItem = menuItem;
                    this.emit('active-changed', menuItem);
                } else if (!active && this._activeMenuItem === menuItem) {
                    this._activeMenuItem = null;
                    this.emit('active-changed', null);
                }
            },
            'notify::sensitive', () => {
                const { sensitive } = menuItem;
                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();
                }
            },
            'activate', () => {
                this.emit('activate', menuItem);
                this.itemActivated(BoxPointer.PopupAnimation.FULL);
            }, GObject.ConnectFlags.AFTER,
            'destroy', () => {
                if (menuItem === this._activeMenuItem)
                    this._activeMenuItem = null;
            }, this);

        this.connectObject('notify::sensitive',
            () => menuItem.syncSensitive(), menuItem);
    }

    _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.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 beforeItem = null;
        if (position == undefined) {
            this.box.add(menuItem.actor);
        } else {
            let items = this._getMenuItems();
            if (position < items.length) {
                beforeItem = items[position].actor;
                this.box.insert_child_below(menuItem.actor, beforeItem);
            } else {
                this.box.add(menuItem.actor);
            }
        }

        if (menuItem instanceof PopupMenuSection) {
            menuItem.connectObject(
                'active-changed', this._subMenuActiveChanged.bind(this),
                'destroy', () => this.length--, this);

            this.connectObject(
                'open-state-changed', (self, open) => {
                    if (open)
                        menuItem.open();
                    else
                        menuItem.close();
                },
                'menu-closed', () => menuItem.emit('menu-closed'),
                'notify::sensitive', () => menuItem.emit('notify::sensitive'),
                menuItem);
        } else if (menuItem instanceof PopupSubMenuMenuItem) {
            if (beforeItem == null)
                this.box.add(menuItem.menu.actor);
            else
                this.box.insert_child_below(menuItem.menu.actor, beforeItem);

            this._connectItemSignals(menuItem);
            menuItem.menu.connectObject('active-changed',
                this._subMenuActiveChanged.bind(this), this);
            this.connectObject('menu-closed', () => {
                menuItem.menu.close(BoxPointer.PopupAnimation.NONE);
            }, menuItem);
        } 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.
            this.connectObject('open-state-changed', () => {
                this._updateSeparatorVisibility(menuItem);
            }, menuItem);
        } else if (menuItem instanceof PopupBaseMenuItem) {
            this._connectItemSignals(menuItem);
        } else {
            throw TypeError("Invalid argument to PopupMenuBase.addMenuItem()");
        }

        menuItem._setParent(this);

        this.length++;
    }

    _getMenuItems() {
        return this.box.get_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.disconnectObject(this);
    }
};
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);
        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.sourceActor.connectObject(
                'key-press-event', this._onKeyPress.bind(this),
                'notify::mapped', () => {
                    if (!this.sourceActor.mapped)
                        this.close();
                }, this);
        }

        this._systemModalOpenedId = 0;
        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 == 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;

        if (!this._systemModalOpenedId) {
            this._systemModalOpenedId =
                Main.layoutManager.connect('system-modal-opened', () => this.close());
        }

        this.isOpen = true;

        this._boxPointer.setPosition(this.sourceActor, this._arrowAlignment);
        this._boxPointer.open(animate);

        this.actor.get_parent().set_child_above_sibling(this.actor, null);

        this.emit('open-state-changed', true);
    }

    close(animate) {
        if (this._activeMenuItem)
            this._activeMenuItem.active = false;

        if (this._boxPointer.visible) {
            this._boxPointer.close(animate, () => {
                this.emit('menu-closed');
            });
        }

        if (!this.isOpen)
            return;

        this.isOpen = false;
        this.emit('open-state-changed', false);
    }

    destroy() {
        this.sourceActor?.disconnectObject(this);

        if (this._systemModalOpenedId)
            Main.layoutManager.disconnect(this._systemModalOpenedId);
        this._systemModalOpenedId = 0;

        super.destroy();
    }
};

var PopupDummyMenu = class {
    constructor(sourceActor) {
        this.sourceActor = sourceActor;
        this.actor = sourceActor;
        this.actor._delegate = this;
    }

    getSensitive() {
        return true;
    }

    get sensitive() {
        return this.getSensitive();
    }

    open() {
        if (this.isOpen)
            return;
        this.isOpen = true;
        this.emit('open-state-changed', true);
    }

    close() {
        if (!this.isOpen)
            return;
        this.isOpen = false;
        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 [, 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.sensitive;
    }

    get sensitive() {
        return this.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 [, naturalHeight] = this.actor.get_preferred_height(-1);
            this.actor.height = 0;
            this.actor.ease({
                height: naturalHeight,
                duration: 250,
                mode: Clutter.AnimationMode.EASE_OUT_EXPO,
                onComplete: () => this.actor.set_height(-1),
            });
            this._arrow.ease({
                rotation_angle_z: targetAngle,
                duration: 250,
                mode: Clutter.AnimationMode.EASE_OUT_EXPO,
            });
        } 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.active = false;

        if (animate && this._needsScrollbar())
            animate = false;

        if (animate) {
            this.actor.ease({
                height: 0,
                duration: 250,
                mode: Clutter.AnimationMode.EASE_OUT_EXPO,
                onComplete: () => {
                    this.actor.hide();
                    this.actor.set_height(-1);
                },
            });
            this._arrow.ease({
                rotation_angle_z: 0,
                duration: 250,
                mode: Clutter.AnimationMode.EASE_OUT_EXPO,
            });
        } 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.active = 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;

        this.actor.add_style_class_name('popup-menu-section');
    }

    // 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 = GObject.registerClass(
class PopupSubMenuMenuItem extends PopupBaseMenuItem {
    _init(text, wantIcon) {
        super._init();

        this.add_style_class_name('popup-submenu-menu-item');

        if (wantIcon) {
            this.icon = new St.Icon({ style_class: 'popup-menu-icon' });
            this.add_child(this.icon);
        }

        this.label = new St.Label({
            text,
            y_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this.add_child(this.label);
        this.label_actor = this.label;

        let expander = new St.Bin({
            style_class: 'popup-menu-item-expander',
            x_expand: true,
        });
        this.add_child(expander);

        this._triangle = arrowIcon(St.Side.RIGHT);
        this._triangle.pivot_point = new Graphene.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.add_child(this._triangleBin);
        this.add_accessible_state(Atk.StateType.EXPANDABLE);

        this.menu = new PopupSubMenu(this, this._triangle);
        this.menu.connect('open-state-changed', this._subMenuOpenStateChanged.bind(this));
        this.connect('destroy', () => this.menu.destroy());
    }

    _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.add_style_pseudo_class('open');
            this._getTopMenu()._setOpenedSubMenu(this.menu);
            this.add_accessible_state(Atk.StateType.EXPANDED);
            this.add_style_pseudo_class('checked');
        } else {
            this.remove_style_pseudo_class('open');
            this._getTopMenu()._setOpenedSubMenu(null);
            this.remove_accessible_state(Atk.StateType.EXPANDED);
            this.remove_style_pseudo_class('checked');
        }
    }

    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;
    }

    vfunc_key_press_event(keyPressEvent) {
        let symbol = keyPressEvent.keyval;

        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.vfunc_key_press_event(keyPressEvent);
    }

    activate(_event) {
        this._setOpenState(true);
    }

    vfunc_button_release_event() {
        // Since we override the parent, we need to manage what the parent does
        // with the active style class
        this.remove_style_pseudo_class('active');
        this._setOpenState(!this._getOpenState());
        return Clutter.EVENT_PROPAGATE;
    }

    vfunc_touch_event(touchEvent) {
        if (touchEvent.type == Clutter.EventType.TOUCH_END) {
            // Since we override the parent, we need to manage what the parent does
            // with the active style class
            this.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) {
        this._grabParams = Params.parse(grabParams,
            { actionMode: Shell.ActionMode.POPUP });
        global.stage.connect('notify::key-focus', () => {
            if (!this.activeMenu)
                return;

            let actor = global.stage.get_key_focus();
            let newMenu = this._findMenuForSource(actor);

            if (newMenu)
                this._changeMenu(newMenu);
        });
        this._menus = [];
    }

    addMenu(menu, position) {
        if (this._menus.includes(menu))
            return;

        menu.connectObject(
            'open-state-changed', this._onMenuOpenState.bind(this),
            'destroy', () => this.removeMenu(menu), this);
        menu.actor.connectObject('captured-event',
            this._onCapturedEvent.bind(this), this);

        if (position == undefined)
            this._menus.push(menu);
        else
            this._menus.splice(position, 0, menu);
    }

    removeMenu(menu) {
        if (menu === this.activeMenu) {
            Main.popModal(this._grab);
            this._grab = null;
        }

        const position = this._menus.indexOf(menu);
        if (position == -1) // not a menu we manage
            return;

        menu.disconnectObject(this);
        menu.actor.disconnectObject(this);

        this._menus.splice(position, 1);
    }

    ignoreRelease() {
    }

    _onMenuOpenState(menu, open) {
        if (open && this.activeMenu === menu)
            return;

        if (open) {
            const oldMenu = this.activeMenu;
            const oldGrab = this._grab;
            this._grab = Main.pushModal(menu.actor, this._grabParams);
            this.activeMenu = menu;
            oldMenu?.close(BoxPointer.PopupAnimation.FADE);
            if (oldGrab)
                Main.popModal(oldGrab);
        } else if (this.activeMenu === menu) {
            this.activeMenu = null;
            Main.popModal(this._grab);
            this._grab = null;
        }
    }

    _changeMenu(newMenu) {
        newMenu.open(this.activeMenu
            ? BoxPointer.PopupAnimation.FADE
            : BoxPointer.PopupAnimation.FULL);
    }

    _onCapturedEvent(actor, event) {
        let menu = actor._delegate;
        const targetActor = global.stage.get_event_actor(event);

        if (event.type() === Clutter.EventType.KEY_PRESS) {
            let symbol = event.get_key_symbol();
            if (symbol === Clutter.KEY_Down &&
                global.stage.get_key_focus() === menu.actor) {
                actor.navigate_focus(null, St.DirectionType.TAB_FORWARD, false);
                return Clutter.EVENT_STOP;
            } else if (symbol === Clutter.KEY_Escape && menu.isOpen) {
                menu.close(BoxPointer.PopupAnimation.FULL);
                return Clutter.EVENT_STOP;
            }
        } else if (event.type() === Clutter.EventType.ENTER &&
                   (event.get_flags() & Clutter.EventFlags.FLAG_GRAB_NOTIFY) === 0) {
            let hoveredMenu = this._findMenuForSource(targetActor);

            if (hoveredMenu && hoveredMenu !== menu)
                this._changeMenu(hoveredMenu);
        } else if ((event.type() === Clutter.EventType.BUTTON_PRESS ||
                    event.type() === Clutter.EventType.TOUCH_BEGIN) &&
                   !actor.contains(targetActor)) {
            menu.close(BoxPointer.PopupAnimation.FULL);
        }

        return Clutter.EVENT_PROPAGATE;
    }

    _findMenuForSource(source) {
        while (source) {
            let actor = source;
            const menu = this._menus.find(m => m.sourceActor === actor);
            if (menu)
                return menu;
            source = source.get_parent();
        }

        return null;
    }

    _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)core.js�$// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported run, script_overviewShowStart, script_overviewShowDone,
            script_applicationsShowStart, script_applicationsShowDone,
            script_afterShowHide, malloc_usedSize, glx_swapComplete,
            clutter_stagePaintDone */
/* eslint camelcase: ["error", { properties: "never", allow: ["^script_", "^malloc", "^glx", "^clutter"] }] */

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',
    },
};

const 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',
}];

async function run() {
    /* eslint-disable no-await-in-loop */
    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');
    });

    await 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];
            await Scripting.destroyTestWindows();

            for (let k = 0; k < config.count; k++) {
                await Scripting.createTestWindow({
                    width: config.width,
                    height: config.height,
                    alpha: config.alpha,
                    maximized: config.maximized,
                });
            }

            await Scripting.waitTestWindows();
            await Scripting.sleep(1000);
            await Scripting.waitLeisure();
        }

        Scripting.scriptEvent('overviewShowStart');
        Main.overview.show();

        await Scripting.waitLeisure();
        Main.overview.hide();
        await Scripting.waitLeisure();

        System.gc();
        await Scripting.sleep(1000);
        Scripting.collectStatistics();
        Scripting.scriptEvent('afterShowHide');
    }

    await Scripting.destroyTestWindows();
    await Scripting.sleep(1000);

    Main.overview.show();
    await Scripting.waitLeisure();

    for (let i = 0; i < 2; i++) {
        Scripting.scriptEvent('applicationsShowStart');
        // eslint-disable-next-line require-atomic-updates
        Main.overview.dash.showAppsButton.checked = true;
        await Scripting.waitLeisure();
        Scripting.scriptEvent('applicationsShowDone');
        // eslint-disable-next-line require-atomic-updates
        Main.overview.dash.showAppsButton.checked = false;
        await Scripting.waitLeisure();
    }
    /* eslint-enable no-await-in-loop */
}

let showingOverview = false;
let finishedShowingOverview = false;
let overviewShowStart;
let overviewFrames;
let overviewLatency;
let mallocUsedSize = 0;
let overviewShowCount = 0;
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)weather.js)// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-

const { Geoclue, Gio, GLib, GWeather, Shell } = imports.gi;
const Signals = imports.signals;

const PermissionStore = imports.misc.permissionStore;

const { loadInterfaceXML } = imports.misc.fileUtils;

Gio._promisify(Geoclue.Simple, 'new');

const WeatherIntegrationIface = loadInterfaceXML('org.gnome.Shell.WeatherIntegration');

const WEATHER_BUS_NAME = 'org.gnome.Weather';
const WEATHER_OBJECT_PATH = '/org/gnome/Weather';
const WEATHER_INTEGRATION_IFACE = 'org.gnome.Shell.WeatherIntegration';

const WEATHER_APP_ID = 'org.gnome.Weather.desktop';

// 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._needsAuth = true;
        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, err) => {
                if (err)
                    log(`Error looking up permission: ${err.message}`);

                let [perms, data] = err ? [{}, 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();

        const providers =
            GWeather.Provider.METAR |
            GWeather.Provider.MET_NO |
            GWeather.Provider.OWM;
        this._weatherInfo = new GWeather.Info({
            application_id: 'org.gnome.Shell',
            contact_info: 'https://gitlab.gnome.org/GNOME/gnome-shell/-/raw/HEAD/gnome-shell.doap',
            enabled_providers: providers,
        });
        this._weatherInfo.connect_after('updated', () => {
            this._lastUpdate = GLib.DateTime.new_now_local();
            this.emit('changed');
        });

        this._weatherApp = null;
        this._weatherProxy = null;

        this._createWeatherProxy();

        this._settings = new Gio.Settings({
            schema_id: 'org.gnome.shell.weather',
        });
        this._settings.connect('changed::automatic-location',
            this._onAutomaticLocationChanged.bind(this));
        this._onAutomaticLocationChanged();
        this._settings.connect('changed::locations',
            this._onLocationsChanged.bind(this));
        this._onLocationsChanged();

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

    get available() {
        return this._weatherApp != null;
    }

    get loading() {
        return this._loading;
    }

    get hasLocation() {
        return this._locationValid;
    }

    get info() {
        return this._weatherInfo;
    }

    activateApp() {
        if (this._weatherApp)
            this._weatherApp.activate();
    }

    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._needsAuth || this._weatherAuthorized);
    }

    async _createWeatherProxy() {
        const nodeInfo = Gio.DBusNodeInfo.new_for_xml(WeatherIntegrationIface);
        try {
            this._weatherProxy = await Gio.DBusProxy.new(
                Gio.DBus.session,
                Gio.DBusProxyFlags.DO_NOT_AUTO_START | Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
                nodeInfo.lookup_interface(WEATHER_INTEGRATION_IFACE),
                WEATHER_BUS_NAME,
                WEATHER_OBJECT_PATH,
                WEATHER_INTEGRATION_IFACE,
                null);
        } catch (e) {
            log(`Failed to create GNOME Weather proxy: ${e}`);
            return;
        }

        this._weatherProxy.connect('g-properties-changed',
            this._onWeatherPropertiesChanged.bind(this));
        this._onWeatherPropertiesChanged();
    }

    _onWeatherPropertiesChanged() {
        if (this._weatherProxy.g_name_owner == null)
            return;

        this._settings.set_boolean('automatic-location',
            this._weatherProxy.AutomaticLocation);
        this._settings.set_value('locations',
            new GLib.Variant('av', this._weatherProxy.Locations));
    }

    _onInstalledChanged() {
        let hadApp = this._weatherApp != null;
        this._weatherApp = this._appSystem.lookup_app(WEATHER_APP_ID);
        let haveApp = this._weatherApp != null;

        if (hadApp !== haveApp)
            this.emit('changed');

        let neededAuth = this._needsAuth;
        this._needsAuth = this._weatherApp === null ||
                          this._weatherApp.app_info.has_key('X-Flatpak');

        if (neededAuth !== this._needsAuth)
            this._updateAutoLocation();
    }

    _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;

        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;
        }
    }

    async _startGClueService() {
        if (this._gclueStarting)
            return;

        this._gclueStarting = true;

        try {
            this._gclueService = await Geoclue.Simple.new(
                'org.gnome.Shell', Geoclue.AccuracyLevel.CITY, null);
        } 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() {
        let useAutoLocation = this._settings.get_boolean('automatic-location');
        if (this._autoLocationRequested == useAutoLocation)
            return;

        this._autoLocationRequested = useAutoLocation;

        this._updateAutoLocation();
    }

    _updateAutoLocation() {
        this._updateLocationMonitoring();

        if (this._useAutoLocation)
            this._startGClueService();
        else
            this._setLocation(this._mostRecentLocation);
    }

    _onLocationsChanged() {
        let locations = this._settings.get_value('locations').deep_unpack();
        let serialized = locations.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)loginDialog.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported LoginDialog */
/*
 * 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 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 UserWidget = imports.ui.userWidget;

const _FADE_ANIMATION_TIME = 250;
const _SCROLL_ANIMATION_TIME = 500;
const _TIMED_LOGIN_IDLE_THRESHOLD = 5.0;

var UserListItem = GObject.registerClass({
    Signals: { 'activate': {} },
}, class UserListItem extends St.Button {
    _init(user) {
        let layout = new St.BoxLayout({
            vertical: true,
        });
        super._init({
            style_class: 'login-dialog-user-list-item',
            button_mask: St.ButtonMask.ONE | St.ButtonMask.THREE,
            can_focus: true,
            x_expand: true,
            child: layout,
            reactive: true,
        });

        this.user = user;
        this.user.connectObject('changed', this._onUserChanged.bind(this), this);

        this.connect('notify::hover', () => {
            this._setSelected(this.hover);
        });

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

        this._userWidget.bind_property('label-actor', this, '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._onUserChanged();
    }

    vfunc_key_focus_in() {
        super.vfunc_key_focus_in();
        this._setSelected(true);
    }

    vfunc_key_focus_out() {
        super.vfunc_key_focus_out();
        this._setSelected(false);
    }

    _onUserChanged() {
        this._updateLoggedIn();
    }

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

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

    _setSelected(selected) {
        if (selected) {
            this.add_style_pseudo_class('selected');
            this.grab_key_focus();
        } else {
            this.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.;
    }
});

var UserList = GObject.registerClass({
    Signals: {
        'activate': { param_types: [UserListItem.$gtype] },
        'item-added': { param_types: [UserListItem.$gtype] },
    },
}, class UserList extends St.ScrollView {
    _init() {
        super._init({
            style_class: 'login-dialog-user-list-view',
            x_expand: true,
            y_expand: true,
        });
        this.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.add_actor(this._box);
        this._items = {};
    }

    vfunc_key_focus_in() {
        super.vfunc_key_focus_in();
        this._moveFocusToItems();
    }

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

        if (!hasItems)
            return;

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

        let focusSet = this.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) {
        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.sync_hover();
        }
    }

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

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

        let value = (box.y1 + adjustment.step_increment / 2.0) - (adjustment.page_size / 2.0);
        adjustment.ease(value, {
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            duration: _SCROLL_ANIMATION_TIME,
        });
    }

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

        let adjustment = this.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_child(item);

        this._items[userName] = item;

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

        // Try to keep the focused item front-and-center
        item.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.destroy();
        delete this._items[userName];
    }

    numItems() {
        return Object.keys(this._items).length;
    }
});

var SessionMenuButton = GObject.registerClass({
    Signals: { 'session-activated': { param_types: [GObject.TYPE_STRING] } },
}, class SessionMenuButton extends St.Bin {
    _init() {
        let gearIcon = new St.Icon({ icon_name: 'emblem-system-symbolic' });
        let button = new St.Button({
            style_class: 'modal-dialog-button button login-dialog-session-list-button',
            reactive: true,
            track_hover: true,
            can_focus: true,
            accessible_name: _("Choose Session"),
            accessible_role: Atk.Role.MENU,
            x_align: Clutter.ActorAlign.CENTER,
            y_align: Clutter.ActorAlign.CENTER,
            child: gearIcon,
        });

        super._init({ child: button });
        this._button = button;

        this._menu = new PopupMenu.PopupMenu(this._button, 0, St.Side.BOTTOM);
        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(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.opacity = sensitive ? 255 : 0;
        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);
            });
        }
    }
});

var LoginDialog = GObject.registerClass({
    Signals: {
        'failed': {},
        'wake-up-screen': {},
    },
}, 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._textureCache.connectObject('texture-file-changed',
            this._updateLogoTexture.bind(this), 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_child(this._userList);

        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);

        // 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: Clutter.ActorAlign.START,
            label_actor: notListedLabel,
        });

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

        this._notListedButton.hide();

        this._userSelectionBox.add_child(this._notListedButton);

        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._sessionMenuButton = new SessionMenuButton();
        this._sessionMenuButton.connect('session-activated',
            (list, sessionId) => {
                this._greeter.call_select_session_sync(sessionId, null);
            });
        this._sessionMenuButton.opacity = 0;
        this._sessionMenuButton.show();
        this.add_child(this._sessionMenuButton);

        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._disableUserList = undefined;
        this._userListLoaded = false;

        this._realmManager = new Realmd.Manager();
        this._realmManager.connectObject('login-format-changed',
            this._showRealmLoginHint.bind(this), 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
        Main.layoutManager.connectObject('startup-complete',
            this._updateDisableUserList.bind(this), this);
    }

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

        let [, , 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 [, , 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;
    }

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

        let [, , natWidth, natHeight] = this._sessionMenuButton.get_preferred_size();

        if (this.get_text_direction() === Clutter.TextDirection.RTL)
            actorBox.x1 = dialogBox.x1 + natWidth;
        else
            actorBox.x1 = dialogBox.x2 - (natWidth * 2);

        actorBox.y1 = dialogBox.y2 - (natHeight * 2);
        actorBox.x2 = actorBox.x1 + natWidth;
        actorBox.y2 = actorBox.y1 + natHeight;

        return actorBox;
    }

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

        let [, , 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) {
        this.set_allocation(dialogBox);

        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;
        if (this._bannerView.visible) {
            bannerAllocation = this._getBannerAllocation(dialogBox);
            bannerHeight = bannerAllocation.y2 - bannerAllocation.y1;
        }

        let authPromptAllocation = null;
        let authPromptWidth = 0;
        if (this._authPrompt.visible) {
            authPromptAllocation = this._getCenterActorAllocation(dialogBox, this._authPrompt);
            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;
        }

        let sessionMenuButtonAllocation = null;
        if (this._sessionMenuButton.visible)
            sessionMenuButtonAllocation = this._getSessionMenuButtonAllocation(dialogBox);

        // 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 accommodate
                    // but don't let it get too close to the logo
                    let [, 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);

        if (authPromptAllocation)
            this._authPrompt.allocate(authPromptAllocation);

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

        if (logoAllocation)
            this._logoBin.allocate(logoAllocation);

        if (sessionMenuButtonAllocation)
            this._sessionMenuButton.allocate(sessionMenuButtonAllocation);
    }

    _ensureUserListLoaded() {
        if (!this._userManager.is_loaded) {
            this._userManager.connectObject('notify::is-loaded',
                () => {
                    if (this._userManager.is_loaded) {
                        this._userManager.disconnectObject(this);
                        this._loadUserList();
                    }
                });
        } 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();
        this._bannerView.ease({
            opacity: 255,
            duration: _FADE_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
    }

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

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

        this._logoBin.destroy_all_children();
        const resourceScale = this._logoBin.get_resource_scale();
        if (this._logoFile) {
            let scaleFactor = St.ThemeContext.get_for_stage(global.stage).scale_factor;
            this._logoBin.add_child(this._textureCache.load_file_async(this._logoFile,
                                                                       -1, -1,
                                                                       scaleFactor,
                                                                       resourceScale));
        }
    }

    _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() {
        const showSessionMenu = this._shouldShowSessionMenuButton();

        this._sessionMenuButton.updateSensitivity(showSessionMenu);
        this._sessionMenuButton.visible = showSessionMenu;
        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._greeter.connectObject(
                'default-session-name-changed', this._onDefaultSessionChanged.bind(this),
                'session-opened', this._onSessionOpened.bind(this),
                'timed-login-requested', this._onTimedLoginRequested.bind(this), this);
        }
    }

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

        const previousUser = this._user;
        this._user = null;

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

        if (previousUser && beginRequest === AuthPrompt.BeginRequestType.REUSE_USERNAME) {
            this._user = previousUser;
            this._authPrompt.setUser(this._user);
            this._authPrompt.begin({ userName: previousUser.get_user_name() });
        } else 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.visible)
            return;
        this._authPrompt.opacity = 0;
        this._authPrompt.show();
        this._authPrompt.ease({
            opacity: 255,
            duration: _FADE_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
        });
        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.setUser(null);
        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.begin({ userName: answer });
                this._updateCancelButton();
            });
        this._updateCancelButton();

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

    _bindOpacity() {
        this._bindings = Main.layoutManager.uiGroup.get_children()
            .filter(c => c != Main.layoutManager.screenShieldGroup)
            .map(c => this.bind_property('opacity', c, 'opacity', 0));
    }

    _unbindOpacity() {
        this._bindings.forEach(b => b.unbind());
    }

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

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

        this._bindOpacity();
        this.ease({
            opacity: 255,
            duration: _FADE_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => this._unbindOpacity(),
        });
    }

    _gotGreeterSessionProxy(proxy) {
        this._greeterSessionProxy = proxy;
        proxy.connectObject('g-properties-changed', (_proxy, properties) => {
            const activeChanged = !!properties.lookup_value('Active', null);
            if (activeChanged && proxy.Active)
                this._loginScreenSessionActivated();
        }, this);
    }

    _startSession(serviceName) {
        this._bindOpacity();
        this.ease({
            opacity: 0,
            duration: _FADE_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => {
                this._greeter.call_start_session_when_ready_sync(serviceName, true, null);
                this._unbindOpacity();
            },
        });
    }

    _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',
            () => {
                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 null;

                this._timedLoginUserListHold = this._waitForItemForUser(userName);
                return this._timedLoginUserListHold;
            },

            () => {
                this._timedLoginUserListHold = null;

                if (this._disableUserList)
                    loginItem = this._authPrompt;
                else
                    loginItem = 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;
                    return null;
                }
            },

            () => {
                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.visible)
                    this._authPrompt.cancel();

                if (delay > _TIMED_LOGIN_IDLE_THRESHOLD || firstRun) {
                    this._userList.scrollToItem(loginItem);
                    loginItem.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._sessionMenuButton.hide();
        this._setUserListExpanded(true);
        this._notListedButton.show();
        this._userList.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, hold });
        return hold;
    }

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

        this._updateCancelButton();

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

    _onDestroy() {
        if (this._settings) {
            this._settings.run_dispose();
            this._settings = null;
        }
        this._greeter = null;
        this._greeterSessionProxy = null;
        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._userManager.connectObject(
            'user-added', (userManager, user) => {
                this._userList.addUser(user);
                this._updateDisableUserList();
            },
            'user-removed', (userManager, user) => {
                this._userList.removeUser(user);
                this._updateDisableUserList();
            },
            '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();
            }, this);

        return GLib.SOURCE_REMOVE;
    }

    activate() {
        this._userList.grab_key_focus();
        this.show();
    }

    open() {
        Main.ctrlAltTabManager.addGroup(this,
                                        _("Login Window"),
                                        'dialog-password-symbolic',
                                        { sortGroup: CtrlAltTab.SortGroup.MIDDLE });
        this.activate();

        // Clutter doesn't yet fully support invisible parents with forced
        // visible children and will make everything invisible (flicker) on
        // the first frame if we start at 0. So we start at 1 instead...
        this.opacity = 1;
        this._logoBin.set_opacity_override(255);

        this._grab = Main.pushModal(global.stage, { actionMode: Shell.ActionMode.LOGIN_SCREEN });

        this.ease({
            opacity: 255,
            duration: 1000,
            mode: Clutter.AnimationMode.EASE_IN_QUAD,
            onComplete: () => { this._logoBin.set_opacity_override(-1); },
        });

        return true;
    }

    close() {
        Main.popModal(this._grab);
        this._grab = null;
        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)calendar.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported Calendar, CalendarMessageList, DBusEventSource */

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

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

const { loadInterfaceXML } = imports.misc.fileUtils;

var SHOW_WEEKDATE_KEY = 'show-weekdate';

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 _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.includes(date.getDay().toString());
}

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) {
    const ret = _getBeginningOfDay(date);
    ret.setDate(ret.getDate() + 1);
    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) {
        this.id = id;
        this.date = date;
        this.end = end;
        this.summary = summary;
    }
};

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

var EventSourceBase = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
    Properties: {
        'has-calendars': GObject.ParamSpec.boolean(
            'has-calendars', 'has-calendars', 'has-calendars',
            GObject.ParamFlags.READABLE,
            false),
        'is-loading': GObject.ParamSpec.boolean(
            'is-loading', 'is-loading', 'is-loading',
            GObject.ParamFlags.READABLE,
            false),
    },
    Signals: { 'changed': {} },
}, class EventSourceBase extends GObject.Object {
    get isLoading() {
        throw new GObject.NotImplementedError(`isLoading in ${this.constructor.name}`);
    }

    get hasCalendars() {
        throw new GObject.NotImplementedError(`hasCalendars in ${this.constructor.name}`);
    }

    destroy() {
    }

    requestRange(_begin, _end) {
        throw new GObject.NotImplementedError(`requestRange in ${this.constructor.name}`);
    }

    getEvents(_begin, _end) {
        throw new GObject.NotImplementedError(`getEvents in ${this.constructor.name}`);
    }

    hasEvents(_day) {
        throw new GObject.NotImplementedError(`hasEvents in ${this.constructor.name}`);
    }
});

var EmptyEventSource = GObject.registerClass(
class EmptyEventSource extends EventSourceBase {
    get isLoading() {
        return false;
    }

    get hasCalendars() {
        return false;
    }

    requestRange(_begin, _end) {
    }

    getEvents(_begin, _end) {
        let result = [];
        return result;
    }

    hasEvents(_day) {
        return false;
    }
});

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;
}

/**
 * Checks whether an event overlaps a given interval
 *
 * @param {Date} e0 Beginning of the event
 * @param {Date} e1 End of the event
 * @param {Date} i0 Beginning of the interval
 * @param {Date} i1 End of the interval
 * @returns {boolean} Whether there was an overlap
 */
function _eventOverlapsInterval(e0, e1, i0, i1) {
    // This also ensures zero-length events are included
    if (e0 >= i0 && e1 < i1)
        return true;

    if (e1 <= i0)
        return false;
    if (i1 <= e0)
        return false;

    return true;
}

// an implementation that reads data from a session bus service
var DBusEventSource = GObject.registerClass(
class DBusEventSource extends EventSourceBase {
    _init() {
        super._init();
        this._resetCache();
        this._isLoading = false;

        this._initialized = false;
        this._dbusProxy = new CalendarServer();
        this._initProxy();
    }

    async _initProxy() {
        let loaded = false;

        try {
            await this._dbusProxy.init_async(GLib.PRIORITY_DEFAULT, null);
            loaded = true;
        } catch (e) {
            // 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)
            if (!e.matches(Gio.DBusError, Gio.DBusError.TIMED_OUT)) {
                log(`Error loading calendars: ${e.message}`);
                return;
            }
        }

        this._dbusProxy.connectSignal('EventsAddedOrUpdated',
            this._onEventsAddedOrUpdated.bind(this));
        this._dbusProxy.connectSignal('EventsRemoved',
            this._onEventsRemoved.bind(this));
        this._dbusProxy.connectSignal('ClientDisappeared',
            this._onClientDisappeared.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.notify('has-calendars');
        });

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

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

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

    get isLoading() {
        return this._isLoading;
    }

    _resetCache() {
        this._events = new Map();
        this._lastRequestBegin = null;
        this._lastRequestEnd = null;
    }

    _removeMatching(uidPrefix) {
        let changed = false;
        for (const id of this._events.keys()) {
            if (id.startsWith(uidPrefix))
                changed = this._events.delete(id) || changed;
        }
        return changed;
    }

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

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

    _onEventsAddedOrUpdated(dbusProxy, nameOwner, argArray) {
        const [appointments = []] = argArray;
        let changed = false;
        const handledRemovals = new Set();

        for (let n = 0; n < appointments.length; n++) {
            const [id, summary, startTime, endTime] = appointments[n];
            const date = new Date(startTime * 1000);
            const end = new Date(endTime * 1000);
            let event = new CalendarEvent(id, date, end, summary);
            /* It's a recurring event */
            if (!id.endsWith('\n')) {
                const parentId = id.substr(0, id.lastIndexOf('\n') + 1);
                if (!handledRemovals.has(parentId)) {
                    handledRemovals.add(parentId);
                    this._removeMatching(parentId);
                }
            }
            this._events.set(event.id, event);

            changed = true;
        }

        if (changed)
            this.emit('changed');
    }

    _onEventsRemoved(dbusProxy, nameOwner, argArray) {
        const [ids = []] = argArray;

        let changed = false;
        for (const id of ids)
            changed = this._removeMatching(id) || changed;

        if (changed)
            this.emit('changed');
    }

    _onClientDisappeared(dbusProxy, nameOwner, argArray) {
        let [sourceUid = ''] = argArray;
        sourceUid += '\n';

        if (this._removeMatching(sourceUid))
            this.emit('changed');
    }

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

        if (this._curRequestBegin && this._curRequestEnd) {
            if (forceReload) {
                this._events.clear();
                this.emit('changed');
            }
            this._dbusProxy.SetTimeRangeRemote(
                this._curRequestBegin.getTime() / 1000,
                this._curRequestEnd.getTime() / 1000,
                forceReload,
                Gio.DBusCallFlags.NONE);
        }
    }

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

    *_getFilteredEvents(begin, end) {
        for (const event of this._events.values()) {
            if (_eventOverlapsInterval(event.date, event.end, begin, end))
                yield event;
        }
    }

    getEvents(begin, end) {
        let result = [...this._getFilteredEvents(begin, end)];

        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);

        const { done } = this._getFilteredEvents(dayBegin, dayEnd).next();
        return !done;
    }
});

var Calendar = GObject.registerClass({
    Signals: { 'selected-date-changed': { param_types: [GLib.DateTime.$gtype] } },
}, class Calendar extends St.Widget {
    _init() {
        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;

        super._init({
            style_class: 'calendar',
            layout_manager: new Clutter.GridLayout(),
            reactive: true,
        });

        this._buildHeader();
    }

    setEventSource(eventSource) {
        if (!(eventSource instanceof EventSourceBase))
            throw new Error('Event source is not valid type');

        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;

        let datetime = GLib.DateTime.new_from_unix_local(
            this._selectedDate.getTime() / 1000);
        this.emit('selected-date-changed', datetime);

        this._update();
    }

    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.layout_manager;
        let offsetCols = this._useWeekdate ? 1 : 0;
        this.destroy_all_children();

        // Top line of the calendar '<| September 2009 |>'
        this._topBox = new St.BoxLayout({ style_class: 'calendar-month-header' });
        layout.attach(this._topBox, 0, 0, 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,
            x_align: Clutter.ActorAlign.CENTER,
            x_expand: true,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._topBox.add_child(this._monthLabel);

        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.get_text_direction() == Clutter.TextDirection.RTL)
                col = 6 - (7 + iter.getDay() - this._weekStart) % 7;
            else
                col = offsetCols + (7 + iter.getDay() - this._weekStart) % 7;
            layout.attach(label, col, 1, 1, 1);
            iter.setDate(iter.getDate() + 1);
        }

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

    vfunc_scroll_event(scrollEvent) {
        switch (scrollEvent.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.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.getFullYear(), this._selectedDate.getMonth(), 1);

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

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

        beginDate.setDate(beginDate.getDate() - (weekPadding + daysToWeekStart));

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

            if (this._eventSource instanceof EmptyEventSource)
                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.attach(button, col, row, 1, 1);

            this._buttons.push(button);

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

            iter.setDate(iter.getDate() + 1);

            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');
            }
        });
    }
});

var NotificationMessage = GObject.registerClass(
class NotificationMessage extends MessageList.Message {
    _init(notification) {
        super._init(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);
        });
        notification.connectObject(
            'updated', this._onUpdated.bind(this),
            'destroy', () => {
                this.notification = null;
                if (!this._closed)
                    this.close();
            }, 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);
    }

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

    canClose() {
        return true;
    }
});

var TimeLabel = GObject.registerClass(
class NotificationTimeLabel extends St.Label {
    _init(datetime) {
        super._init({
            style_class: 'event-time',
            x_align: Clutter.ActorAlign.START,
            y_align: Clutter.ActorAlign.END,
        });
        this._datetime = datetime;
    }

    vfunc_map() {
        this.text = Util.formatTimeSpan(this._datetime);
        super.vfunc_map();
    }
});

var NotificationSection = GObject.registerClass(
class NotificationSection extends MessageList.MessageListSection {
    _init() {
        super._init();

        this._nUrgent = 0;

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

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

    _sourceAdded(tray, source) {
        source.connectObject('notification-added',
            this._onNotificationAdded.bind(this), this);
    }

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

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

        notification.connectObject(
            'destroy', () => {
                if (isUrgent)
                    this._nUrgent--;
            },
            'updated', () => {
                message.setSecondaryActor(new TimeLabel(notification.datetime));
                this.moveMessage(message, isUrgent ? 0 : this._nUrgent, this.mapped);
            }, this);

        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.mapped);
    }

    vfunc_map() {
        this._messages.forEach(message => {
            if (message.notification.urgency != MessageTray.Urgency.CRITICAL)
                message.notification.acknowledged = true;
        });
        super.vfunc_map();
    }
});

var Placeholder = GObject.registerClass(
class Placeholder extends St.BoxLayout {
    _init() {
        super._init({ style_class: 'message-list-placeholder', vertical: true });
        this._date = new Date();

        this._icon = new St.Icon({ icon_name: 'no-notifications-symbolic' });
        this.add_actor(this._icon);

        this._label = new St.Label({ text: _('No Notifications') });
        this.add_actor(this._label);
    }
});

const DoNotDisturbSwitch = GObject.registerClass(
class DoNotDisturbSwitch extends PopupMenu.Switch {
    _init() {
        this._settings = new Gio.Settings({
            schema_id: 'org.gnome.desktop.notifications',
        });

        super._init(this._settings.get_boolean('show-banners'));

        this._settings.bind('show-banners',
            this, 'state',
            Gio.SettingsBindFlags.INVERT_BOOLEAN);

        this.connect('destroy', () => {
            Gio.Settings.unbind(this, 'state');
            this._settings = null;
        });
    }
});

var CalendarMessageList = GObject.registerClass(
class CalendarMessageList extends St.Widget {
    _init() {
        super._init({
            style_class: 'message-list',
            layout_manager: new Clutter.BinLayout(),
            x_expand: true,
            y_expand: true,
        });

        this._placeholder = new Placeholder();
        this.add_actor(this._placeholder);

        let box = new St.BoxLayout({
            vertical: true,
            x_expand: true,
            y_expand: true,
        });
        this.add_actor(box);

        this._scrollView = new St.ScrollView({
            style_class: 'vfade',
            overlay_scrollbars: true,
            x_expand: true, y_expand: true,
        });
        this._scrollView.set_policy(St.PolicyType.NEVER, St.PolicyType.AUTOMATIC);
        box.add_actor(this._scrollView);

        let hbox = new St.BoxLayout({ style_class: 'message-list-controls' });
        box.add_child(hbox);

        const dndLabel = new St.Label({
            text: _('Do Not Disturb'),
            y_align: Clutter.ActorAlign.CENTER,
        });
        hbox.add_child(dndLabel);

        this._dndSwitch = new DoNotDisturbSwitch();
        this._dndButton = new St.Button({
            style_class: 'dnd-button',
            can_focus: true,
            toggle_mode: true,
            child: this._dndSwitch,
            label_actor: dndLabel,
            y_align: Clutter.ActorAlign.CENTER,
        });
        this._dndSwitch.bind_property('state',
            this._dndButton, 'checked',
            GObject.BindingFlags.BIDIRECTIONAL | GObject.BindingFlags.SYNC_CREATE);
        hbox.add_child(this._dndButton);

        this._clearButton = new St.Button({
            style_class: 'message-list-clear-button button',
            label: _('Clear'),
            can_focus: true,
            x_expand: true,
            x_align: Clutter.ActorAlign.END,
        });
        this._clearButton.connect('clicked', () => {
            this._sectionList.get_children().forEach(s => s.clear());
        });
        hbox.add_actor(this._clearButton);

        this._placeholder.bind_property('visible',
            this._clearButton, 'visible',
            GObject.BindingFlags.INVERT_BOOLEAN);

        this._sectionList = new St.BoxLayout({
            style_class: 'message-list-sections',
            vertical: true,
            x_expand: true,
            y_expand: true,
            y_align: Clutter.ActorAlign.START,
        });
        this._sectionList.connectObject(
            'actor-added', this._sync.bind(this),
            'actor-removed', this._sync.bind(this),
            this);
        this._scrollView.add_actor(this._sectionList);

        this._mediaSection = new Mpris.MediaSection();
        this._addSection(this._mediaSection);

        this._notificationSection = new NotificationSection();
        this._addSection(this._notificationSection);

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

    _addSection(section) {
        section.connectObject(
            'notify::visible', this._sync.bind(this),
            'notify::empty', this._sync.bind(this),
            'notify::can-clear', this._sync.bind(this),
            'destroy', () => this._sectionList.remove_actor(section),
            'message-focused', (_s, messageActor) => {
                Util.ensureActorVisibleInScrollView(this._scrollView, messageActor);
            }, this);
        this._sectionList.add_actor(section);
    }

    _sync() {
        let sections = this._sectionList.get_children();
        let visible = sections.some(s => s.allowed);
        this.visible = visible;
        if (!visible)
            return;

        let empty = sections.every(s => s.empty || !s.visible);
        this._placeholder.visible = empty;

        let canClear = sections.some(s => s.canClear && s.visible);
        this._clearButton.reactive = canClear;
    }
});
(uuay)grabHelper.jsr$// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported GrabHelper */

const { Clutter, St } = imports.gi;

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

// 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) {
        if (!(owner instanceof Clutter.Actor))
            throw new Error('GrabHelper owner must be a Clutter.Actor');

        this._owner = owner;
        this._modalParams = params;

        this._grabStack = [];

        this._ignoreUntilRelease = false;

        this._modalCount = 0;
    }

    _isWithinGrabbedActor(actor) {
        let currentActor = this.currentGrab.actor;
        while (actor) {
            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;
    }

    grabAsync(params) {
        return new Promise((resolve, reject) => {
            params.onUngrab = resolve;

            if (!this.grab(params))
                reject(new Error('Grab failed'));
        });
    }

    _takeModalGrab() {
        let firstGrab = this._modalCount == 0;
        if (firstGrab) {
            let grab = Main.pushModal(this._owner, this._modalParams);
            if (grab.get_seat_state() !== Clutter.GrabState.ALL) {
                Main.popModal(grab);
                return false;
            }

            this._grab = grab;
            this._capturedEventId = this._owner.connect('captured-event',
                (actor, event) => {
                    return this.onCapturedEvent(event);
                });
        }

        this._modalCount++;
        return true;
    }

    _releaseModalGrab() {
        this._modalCount--;
        if (this._modalCount > 0)
            return;

        this._owner.disconnect(this._capturedEventId);
        this._ignoreUntilRelease = false;

        Main.popModal(this._grab);
        this._grab = null;
    }

    // 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_PROPAGATE;
        }

        const targetActor = global.stage.get_event_actor(event);

        if (type === Clutter.EventType.ENTER ||
            type === Clutter.EventType.LEAVE ||
            this.currentGrab.actor.contains(targetActor))
            return Clutter.EVENT_PROPAGATE;

        if (Main.keyboard.maybeHandleEvent(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(targetActor) + 1;
            this.ungrab({ actor: this._grabStack[i].actor, isUser: true });
            return Clutter.EVENT_STOP;
        }

        return Clutter.EVENT_STOP;
    }
};
(uuay)credentialManager.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported CredentialManager */

var CredentialManager = class CredentialManager {
    constructor(service) {
        this._token = null;
        this._service = service;
    }

    get token() {
        return this._token;
    }

    set token(t) {
        this._token = t;
        if (this._token)
            this.emit('user-authenticated', this._token);
    }

    get service() {
        return this._service;
    }
};
(uuay)backgroundMenu.js7	// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported addBackgroundMenu */

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._backgroundManager.addMenu(actor._backgroundMenu);

    function openMenu(x, y) {
        Main.layoutManager.setDummyCursorGeometry(x, y, 0, 0);
        actor._backgroundMenu.open(BoxPointer.PopupAnimation.FULL);
    }

    let clickAction = new Clutter.ClickAction();
    clickAction.connect('long-press', (action, theActor, 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);

    global.display.connectObject('grab-op-begin',
        () => clickAction.release(), actor);

    actor.connect('destroy', () => {
        actor._backgroundMenu.destroy();
        actor._backgroundMenu = null;
        actor._backgroundManager = null;
    });
}
(uuay)welcomeDialog.jsM// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WelcomeDialog */

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

const Config = imports.misc.config;
const Dialog = imports.ui.dialog;
const Main = imports.ui.main;
const ModalDialog = imports.ui.modalDialog;

var DialogResponse = {
    NO_THANKS: 0,
    TAKE_TOUR: 1,
};

var WelcomeDialog = GObject.registerClass(
class WelcomeDialog extends ModalDialog.ModalDialog {
    _init() {
        super._init({ styleClass: 'welcome-dialog' });

        const appSystem = Shell.AppSystem.get_default();
        this._tourAppInfo = appSystem.lookup_app('org.gnome.Tour.desktop');

        this._buildLayout();
    }

    open() {
        if (!this._tourAppInfo)
            return false;

        return super.open();
    }

    _buildLayout() {
        const [majorVersion] = Config.PACKAGE_VERSION.split('.');
        const title = _('Welcome to GNOME %s').format(majorVersion);
        const description = _('If you want to learn your way around, check out the tour.');
        const content = new Dialog.MessageDialogContent({ title, description });

        const icon = new St.Widget({ style_class: 'welcome-dialog-image' });
        content.insert_child_at_index(icon, 0);

        this.contentLayout.add_child(content);

        this.addButton({
            label: _('No Thanks'),
            action: () => this._sendResponse(DialogResponse.NO_THANKS),
            key: Clutter.KEY_Escape,
        });
        this.addButton({
            label: _('Take Tour'),
            action: () => this._sendResponse(DialogResponse.TAKE_TOUR),
        });
    }

    _sendResponse(response) {
        if (response === DialogResponse.TAKE_TOUR) {
            this._tourAppInfo.launch(0, -1, Shell.AppLaunchGpu.APP_PREF);
            Main.overview.hide();
        }

        this.close();
    }
});
(uuay)network.js�// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported NMApplet */
const { Clutter, Gio, GLib, GObject, Meta, NM, Polkit, St } = imports.gi;
const Signals = imports.signals;

const Animation = imports.ui.animation;
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');
Gio._promisify(NM.Client, 'new_async');
Gio._promisify(NM.Client.prototype, 'check_connectivity_async');

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 < 20)
        return 'none';
    else if (value < 40)
        return 'weak';
    else if (value < 50)
        return 'ok';
    else if (value < 80)
        return 'good';
    else
        return 'excellent';
}

function ssidToLabel(ssid) {
    let label = NM.utils_ssid_to_utf8(ssid.get_data());
    if (!label)
        label = _("<unknown>");
    return label;
}

function ensureActiveConnectionProps(active) {
    if (!active._primaryDevice) {
        let devices = active.get_devices();
        if (devices.length > 0) {
            // This list is guaranteed to have at most one device in it.
            let device = 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${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);
        return;
    } catch (e) {
        log(`Failed to launch Settings panel: ${e.message}, trying new name...`);
    }

    try {
        Gio.DBus.session.call(
            'org.gnome.Settings',
            '/org/gnome/Settings',
            '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: ${e.message}`);
    }
}

var NMConnectionItem = class {
    constructor(section, connection) {
        this._section = section;
        this._connection = connection;
        this._activeConnection = null;

        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._activeConnection?.disconnectObject(this);
        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) {
        this._activeConnection?.disconnectObject(this);

        this._activeConnection = activeConnection;

        this._activeConnection?.connectObject('notify::state',
            this._connectionStateChanged.bind(this), this);

        this._sync();
    }
};
Signals.addSignalMethods(NMConnectionItem.prototype);

var NMConnectionSection = class NMConnectionSection {
    constructor(client) {
        if (this.constructor === NMConnectionSection)
            throw new TypeError(`Cannot instantiate abstract type ${this.constructor.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._client.connectObject('notify::connectivity',
            this._iconChanged.bind(this), this);
    }

    destroy() {
        this._client.disconnectObject(this);
        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 every time the 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', (o, 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 NMConnectionDevice extends NMConnectionSection {
    constructor(client, device) {
        super(client);

        if (this.constructor === NMConnectionDevice)
            throw new TypeError(`Cannot instantiate abstract type ${this.constructor.name}`);

        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._device.connectObject(
            'state-changed', this._deviceStateChanged.bind(this),
            'notify::active-connection', this._activeConnectionChanged.bind(this),
            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() {
        this._device.disconnectObject(this);

        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.visible = nItems == 0;
        this._deactivateItem.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);
                    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 ${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.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);

        const settingsPanel = this._useWwanPanel()
            ? 'gnome-wwan-panel.desktop'
            : 'gnome-network-panel.desktop';

        this.item.menu.addSettingsAction(_('Mobile Broadband Settings'), settingsPanel);

        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);

        this._mobileDevice?.connectObject(
            'notify::operator-name', this._sync.bind(this),
            'notify::signal-quality', () => this._iconChanged(), this);

        Main.sessionMode.connectObject('updated',
            this._sessionUpdated.bind(this), this);
        this._sessionUpdated();
    }

    get category() {
        return NMConnectionCategory.WWAN;
    }

    _useWwanPanel() {
        // Currently, wwan panel doesn't support CDMA_EVDO modems
        const supportedCaps =
            NM.DeviceModemCapabilities.GSM_UMTS |
            NM.DeviceModemCapabilities.LTE;
        return this._device.current_capabilities & supportedCaps;
    }

    _autoConnect() {
        if (this._useWwanPanel())
            launchSettingsPanel('wwan', 'show-device', this._device.udi);
        else
            launchSettingsPanel('network', 'connect-3g', this._device.get_path());
    }

    _sessionUpdated() {
        this._autoConnectItem.sensitive = Main.sessionMode.hasWindows;
    }

    destroy() {
        this._mobileDevice?.disconnectObject(this);
        Main.sessionMode.disconnectObject(this);

        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();
    }

    _getMenuIcon() {
        if (!this._device.active_connection)
            return 'network-cellular-disabled-symbolic';

        return this.getIndicatorIcon();
    }

    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");
    }

    _getMenuIcon() {
        if (!this._device.active_connection)
            return 'network-cellular-disabled-symbolic';

        return this.getIndicatorIcon();
    }

    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 = GObject.registerClass({
    Signals: {
        'selected': {},
    },
}, class NMWirelessDialogItem extends St.BoxLayout {
    _init(network) {
        this._network = network;
        this._ap = network.accessPoints[0];

        super._init({
            style_class: 'nm-dialog-item',
            can_focus: true,
            reactive: true,
        });

        let action = new Clutter.ClickAction();
        action.connect('clicked', () => this.grab_key_focus());
        this.add_action(action);

        let title = ssidToLabel(this._ap.get_ssid());
        this._label = new St.Label({
            text: title,
            x_expand: true,
        });

        this.label_actor = this._label;
        this.add_child(this._label);

        this._selectedIcon = new St.Icon({
            style_class: 'nm-dialog-icon nm-dialog-network-selected',
            icon_name: 'object-select-symbolic',
        });
        this.add(this._selectedIcon);

        this._icons = new St.BoxLayout({
            style_class: 'nm-dialog-icons',
            x_align: Clutter.ActorAlign.END,
        });
        this.add_child(this._icons);

        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();
    }

    vfunc_key_focus_in() {
        this.emit('selected');
    }

    _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`;
    }
});

var NMWirelessDialog = GObject.registerClass(
class NMWirelessDialog extends ModalDialog.ModalDialog {
    _init(client, device) {
        super._init({ styleClass: 'nm-dialog' });

        this._client = client;
        this._device = device;

        this._client.connectObject('notify::wireless-enabled',
            this._syncView.bind(this), this);

        this._rfkill = Rfkill.getRfkillManager();
        this._rfkill.connectObject('airplane-mode-changed',
            this._syncView.bind(this), this);

        this._networks = [];
        this._buildLayout();

        let connections = client.get_connections();
        this._connections = connections.filter(
            connection => device.connection_valid(connection));

        device.connectObject(
            'access-point-added', this._accessPointAdded.bind(this),
            'access-point-removed', this._accessPointRemoved.bind(this),
            'notify::active-access-point', this._activeApChanged.bind(this), 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 = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 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();
        });

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

    _onDestroy() {
        if (this._scanTimeoutId) {
            GLib.source_remove(this._scanTimeoutId);
            this._scanTimeoutId = 0;
        }

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

    _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' });

        const icon = new St.Icon({
            style_class: 'nm-dialog-header-icon',
            icon_name: 'network-wireless-symbolic',
        });

        let titleBox = new St.BoxLayout({ vertical: true });
        const title = new St.Label({
            style_class: 'nm-dialog-header',
            text: _('Wi-Fi Networks'),
        });
        const 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(),
            y_expand: true,
        });

        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);
        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_child(this._airplaneIcon);
        this._airplaneBox.add_child(this._airplaneHeadline);
        this._airplaneBox.add_child(this._airplaneText);
        this._airplaneBox.add_child(airplaneSubStack);
        this._stack.add_child(this._airplaneBox);

        this.contentLayout.add_child(this._stack);

        this._disconnectButton = this.addButton({
            action: () => this.close(),
            label: _('Cancel'),
            key: Clutter.KEY_Escape,
        });
        this._connectButton = this.addButton({
            action: this._connect.bind(this),
            label: _('Connect'),
            key: Clutter.KEY_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.disconnectObject(this);
            this._accessPointAdded(this._device, accessPoint);
        }
    }

    _getApSecurityType(accessPoint) {
        if (accessPoint._secType)
            return accessPoint._secType;

        let flags = accessPoint.flags;
        let wpaFlags = accessPoint.wpa_flags;
        let rsnFlags = accessPoint.rsn_flags;
        let type;
        if (rsnFlags != NM80211ApSecurityFlags.NONE) {
            /* RSN check first so that WPA+WPA2 APs are treated as RSN/WPA2 */
            if (rsnFlags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
                type = NMAccessPointSecurity.WPA2_ENT;
            else if (rsnFlags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
                type = NMAccessPointSecurity.WPA2_PSK;
        } else if (wpaFlags != NM80211ApSecurityFlags.NONE) {
            if (wpaFlags & NM80211ApSecurityFlags.KEY_MGMT_802_1X)
                type = NMAccessPointSecurity.WPA_ENT;
            else if (wpaFlags & NM80211ApSecurityFlags.KEY_MGMT_PSK)
                type = NMAccessPointSecurity.WPA_PSK;
        } else {
            // eslint-disable-next-line no-lonely-if
            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.includes(connection))
                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.connectObject('notify::ssid',
                this._notifySsidCb.bind(this), this);
            return;
        }

        let pos = this._findNetwork(accessPoint);
        let network;

        if (pos != -1) {
            network = this._networks[pos];
            if (network.accessPoints.includes(accessPoint)) {
                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, 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);
                }
                this._syncVisibilityId = 0;
                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.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);
        });

        adjustment.value = scrollValue;
    }

    _selectNetwork(network) {
        if (this._selectedNetwork)
            this._selectedNetwork.item.remove_style_pseudo_class('selected');

        this._selectedNetwork = network;
        this._updateSensitivity();

        if (this._selectedNetwork)
            this._selectedNetwork.item.add_style_pseudo_class('selected');
    }

    _createNetworkItem(network) {
        network.item = new NMWirelessDialogItem(network);
        network.item.setActive(network == this._selectedNetwork);
        network.item.hide();
        network.item.connect('selected', () => {
            Util.ensureActorVisibleInScrollView(this._scrollView, network.item);
            this._selectNetwork(network);
        });
        network.item.connect('destroy', () => {
            let keyFocus = global.stage.key_focus;
            if (keyFocus && keyFocus.contains(network.item))
                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._client.connectObject(
            'notify::wireless-enabled', this._sync.bind(this),
            'notify::wireless-hardware-enabled', this._sync.bind(this),
            'notify::connectivity', this._iconChanged.bind(this), this);

        this._device.connectObject(
            'notify::active-access-point', this._activeApChanged.bind(this),
            'state-changed', this._deviceStateChanged.bind(this), this);

        this._sync();
    }

    get category() {
        return NMConnectionCategory.WIRELESS;
    }

    _iconChanged() {
        this._sync();
        this.emit('icon-changed');
    }

    destroy() {
        this._device.disconnectObject(this);
        this._activeAccessPoint?.disconnectObject(this);
        this._client.disconnectObject(this);

        if (this._dialog) {
            this._dialog.destroy();
            this._dialog = null;
        }

        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() {
        this._activeAccessPoint?.disconnectObject(this);

        this._activeAccessPoint = this._device.active_access_point;

        this._activeAccessPoint?.connectObject('notify::strength',
            this._strengthChanged.bind(this), this);

        this._sync();
    }

    _sync() {
        this._toggleItem.label.text = this._client.wireless_enabled ? _("Turn Off") : _("Turn On");
        this._toggleItem.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)
            return ssidToLabel(ap.get_ssid());
        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._client.wireless_enabled)
            return 'network-wireless-disabled-symbolic';

        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) {
        this._activeConnection?.disconnectObject(this);

        this._activeConnection = activeConnection;

        this._activeConnection?.connectObject('vpn-state-changed',
            this._connectionStateChanged.bind(this), 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.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-disabled-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.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.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 '';
    }
};

var NMApplet = GObject.registerClass(
class Indicator extends PanelMenu.SystemIndicator {
    _init() {
        super._init();

        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;

        this._getClient();
    }

    async _getClient() {
        this._client = await NM.Client.new_async(null);

        this._activeConnections = [];
        this._connections = [];
        this._connectivityQueue = [];

        this._mainConnection = null;

        this._notification = null;
        this._PortalNotification = null;

        this._nmDevices = [];
        this._devices = { };

        const 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);

        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));

        try {
            this._configPermission = await Polkit.Permission.new(
                'org.freedesktop.NetworkManager.network-control', null, null);
        } catch (e) {
            log(`No permission to control network connections: ${e}`);
            this._configPermission = null;
        }

        Main.sessionMode.connect('updated', this._sessionUpdated.bind(this));
        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 });
        this._notification.setUrgency(urgency);
        this._notification.setTransient(true);
        this._notification.connect('destroy', () => {
            this._notification = null;
        });
        this._source.showNotification(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.connectObject('activation-failed',
            this._onActivationFailed.bind(this), 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.disconnectObject(this);
        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);
            return connection;
        }

        connection = this._client.get_activating_connection();
        if (connection) {
            ensureActiveConnectionProps(connection);
            return connection;
        }

        return null;
    }

    _syncMainConnection() {
        this._mainConnection?._primaryDevice?.disconnectObject(this);
        this._mainConnection?.disconnectObject(this);

        this._mainConnection = this._getMainConnection();

        if (this._mainConnection) {
            this._mainConnection._primaryDevice?.connectObject('icon-changed',
                this._updateIcon.bind(this), this);
            this._mainConnection.connectObject('notify::state',
                this._mainConnectionStateChanged.bind(this), 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._vpnSection.setActiveConnections(vpnConnections);

        this._updateIcon();
    }

    _mainConnectionStateChanged() {
        if (this._mainConnection.state == NM.ActiveConnectionState.ACTIVATED && this._notification)
            this._notification.destroy();
        if (this._mainConnection.state == NM.ActiveConnectionState.ACTIVATED && this._PortalNotification)
            this._PortalNotification.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 (this._connections.includes(connection)) {
            // connection was already seen
            return;
        }

        connection.connectObject('changed',
            this._updateConnection.bind(this), 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.disconnectObject(this);
    }

    _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.visible = this._client.nm_running;
        this.menu.actor.visible = this._client.networking_enabled;

        this._updateIcon();
        this._syncConnectivity();
    }

    _flushConnectivityQueue() {
        if (this._portalHelperProxy) {
            for (let item of this._connectivityQueue)
                this._portalHelperProxy.CloseRemote(item);
        }

        this._connectivityQueue = [];
    }

    _closeConnectivityCheck(path) {
        let index = this._connectivityQueue.indexOf(path);

        if (index >= 0) {
            if (this._portalHelperProxy)
                this._portalHelperProxy.CloseRemote(path);

            this._connectivityQueue.splice(index, 1);
        }
    }

    async _portalHelperDone(proxy, emitter, parameters) {
        let [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._closeConnectivityCheck(path);
        } else if (result == PortalHelperResult.RECHECK) {
            try {
                const state = await this._client.check_connectivity_async(null);
                if (state >= NM.ConnectivityState.FULL)
                    this._closeConnectivityCheck(path);
            } catch (e) { }
        } else {
            log(`Invalid result from portal helper: ${result}`);
        }
    }

    _syncConnectivity() {
        if (this._mainConnection == null ||
            this._mainConnection.state != NM.ActiveConnectionState.ACTIVATED) {
            this._flushConnectivityQueue();
            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 ||= this._client.connectivity < NM.ConnectivityState.FULL;
        if (!isPortal || Main.sessionMode.isGreeter)
            return;

        let name = this._mainConnection.get_id();
        let path = this._mainConnection.get_path();
        for (let item of this._connectivityQueue) {
            if (item == path)
                return;
        }

        if (this._PortalNotification)
            this._PortalNotification.destroy();
        const source = new MessageTray.Source(
            _('Network Manager'), 'network-wireless-acquiring-symbolic');
        source.policy =
            new MessageTray.NotificationApplicationPolicy('gnome-network-panel')

        this._PortalNotification = new MessageTray.Notification(source,
            _('Sign Into Wi–Fi Network'),
            _(name));
        this._PortalNotification.connect('destroy',
            () => (this._PortalNotification = null))
        this._PortalNotification.connect('activated',
            () => this._onNotificationActivated(path));

        Main.messageTray.add(source);
        source.showNotification(this._PortalNotification)
    }

    async _onNotificationActivated(path) {
        let timestamp = global.get_current_time();
        if (this._portalHelperProxy) {
            this._portalHelperProxy.AuthenticateRemote(path, '', 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: ${error}`);
                        return;
                    }

                    this._portalHelperProxy = proxy;
                    proxy.connectSignal('Done', this._portalHelperDone.bind(this));

                    proxy.AuthenticateRemote(path, '', timestamp);
                });
        }

        this._connectivityQueue.push(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 !== null;
    }
});
(uuay)workspacesView.js��// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
/* exported WorkspacesView, WorkspacesDisplay */

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

const Layout = imports.ui.layout;
const Main = imports.ui.main;
const OverviewControls = imports.ui.overviewControls;
const SwipeTracker = imports.ui.swipeTracker;
const Util = imports.misc.util;
const Workspace = imports.ui.workspace;
const { ThumbnailsBox, MAX_THUMBNAIL_SCALE } = imports.ui.workspaceThumbnail;

var WORKSPACE_SWITCH_TIME = 250;

const MUTTER_SCHEMA = 'org.gnome.mutter';

const WORKSPACE_MIN_SPACING = 24;
const WORKSPACE_MAX_SPACING = 80;

const WORKSPACE_INACTIVE_SCALE = 0.94;

const SECONDARY_WORKSPACE_SCALE = 0.80;

var WorkspacesViewBase = GObject.registerClass({
    GTypeFlags: GObject.TypeFlags.ABSTRACT,
}, class WorkspacesViewBase extends St.Widget {
    _init(monitorIndex, overviewAdjustment) {
        super._init({
            style_class: 'workspaces-view',
            x_expand: true,
            y_expand: true,
        });
        this.connect('destroy', this._onDestroy.bind(this));
        global.focus_manager.add_group(this);

        this._monitorIndex = monitorIndex;

        this._inDrag = false;
        Main.overview.connectObject(
            'window-drag-begin', this._dragBegin.bind(this),
            'window-drag-end', this._dragEnd.bind(this), this);

        this._overviewAdjustment = overviewAdjustment;
        overviewAdjustment.connectObject('notify::value',
            () => this._updateWorkspaceMode(), this);
    }

    _onDestroy() {
        this._dragEnd();
    }

    _dragBegin() {
        this._inDrag = true;
    }

    _dragEnd() {
        this._inDrag = false;
    }

    _updateWorkspaceMode() {
    }

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

        for (const child of this)
            child.allocate_available_size(0, 0, box.get_width(), box.get_height());
    }

    vfunc_get_preferred_width() {
        return [0, 0];
    }

    vfunc_get_preferred_height() {
        return [0, 0];
    }
});

var FitMode = {
    SINGLE: 0,
    ALL: 1,
};

var WorkspacesView = GObject.registerClass(
class WorkspacesView extends WorkspacesViewBase {
    _init(monitorIndex, controls, scrollAdjustment, fitModeAdjustment, overviewAdjustment) {
        let workspaceManager = global.workspace_manager;

        super._init(monitorIndex, overviewAdjustment);

        this._controls = controls;
        this._fitModeAdjustment = fitModeAdjustment;
        this._fitModeAdjustment.connectObject('notify::value', () => {
            this._updateVisibility();
            this._updateWorkspacesState();
            this.queue_relayout();
        }, this);

        this._animating = false; // tweening
        this._gestureActive = false; // touch(pad) gestures

        this._scrollAdjustment = scrollAdjustment;
        this._scrollAdjustment.connectObject('notify::value',
            this._onScrollAdjustmentChanged.bind(this), this);

        this._workspaces = [];
        this._updateWorkspaces();
        workspaceManager.connectObject(
            'notify::n-workspaces', this._updateWorkspaces.bind(this),
            'workspaces-reordered', () => {
                this._workspaces.sort((a, b) => {
                    return a.metaWorkspace.index() - b.metaWorkspace.index();
                });
                this._workspaces.forEach(
                    (ws, i) => this.set_child_at_index(ws, i));
            }, this);

        global.window_manager.connectObject('switch-workspace',
            this._activeWorkspaceChanged.bind(this), this);
        this._updateVisibility();
    }

    _getFirstFitAllWorkspaceBox(box, spacing, vertical) {
        const { nWorkspaces } = global.workspaceManager;
        const [width, height] = box.get_size();
        const [workspace] = this._workspaces;

        const fitAllBox = new Clutter.ActorBox();

        let [x1, y1] = box.get_origin();

        // Spacing here is not only the space between workspaces, but also the
        // space before the first workspace, and after the last one. This prevents
        // workspaces from touching the edges of the allocation box.
        if (vertical) {
            const availableHeight = height - spacing * (nWorkspaces + 1);
            let workspaceHeight = availableHeight / nWorkspaces;
            let [, workspaceWidth] =
                workspace.get_preferred_width(workspaceHeight);

            y1 = spacing;
            if (workspaceWidth > width) {
                [, workspaceHeight] = workspace.get_preferred_height(width);
                y1 += Math.max((availableHeight - workspaceHeight * nWorkspaces) / 2, 0);
            }

            fitAllBox.set_size(width, workspaceHeight);
        } else {
            const availableWidth = width - spacing * (nWorkspaces + 1);
            let workspaceWidth = availableWidth / nWorkspaces;
            let [, workspaceHeight] =
                workspace.get_preferred_height(workspaceWidth);

            x1 = spacing;
            if (workspaceHeight > height) {
                [, workspaceWidth] = workspace.get_preferred_width(height);
                x1 += Math.max((availableWidth - workspaceWidth * nWorkspaces) / 2, 0);
            }

            fitAllBox.set_size(workspaceWidth, height);
        }

        fitAllBox.set_origin(x1, y1);

        return fitAllBox;
    }

    _getFirstFitSingleWorkspaceBox(box, spacing, vertical) {
        const [width, height] = box.get_size();
        const [workspace] = this._workspaces;

        const rtl = this.text_direction === Clutter.TextDirection.RTL;
        const adj = this._scrollAdjustment;
        const currentWorkspace = vertical || !rtl
            ? adj.value : adj.upper - adj.value - 1;

        // Single fit mode implies centered too
        let [x1, y1] = box.get_origin();
        if (vertical) {
            const [, workspaceHeight] = workspace.get_preferred_height(width);
            y1 += (height - workspaceHeight) / 2;
            y1 -= currentWorkspace * (workspaceHeight + spacing);
        } else {
            const [, workspaceWidth] = workspace.get_preferred_width(height);
            x1 += (width - workspaceWidth) / 2;
            x1 -= currentWorkspace * (workspaceWidth + spacing);
        }

        const fitSingleBox = new Clutter.ActorBox({ x1, y1 });

        if (vertical) {
            const [, workspaceHeight] = workspace.get_preferred_height(width);
            fitSingleBox.set_size(width, workspaceHeight);
        } else {
            const [, workspaceWidth] = workspace.get_preferred_width(height);
            fitSingleBox.set_size(workspaceWidth, height);
        }

        return fitSingleBox;
    }

    _getSpacing(box, fitMode, vertical) {
        const [width, height] = box.get_size();
        const [workspace] = this._workspaces;

        let availableSpace;
        let workspaceSize;
        if (vertical) {
            [, workspaceSize] = workspace.get_preferred_height(width);
            availableSpace = (height - workspaceSize) / 2;
        } else {
            [, workspaceSize] = workspace.get_preferred_width(height);
            availableSpace = (width - workspaceSize) / 2;
        }

        const spacing = (availableSpace - workspaceSize * 0.4) * (1 - fitMode);
        const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);

        return Math.clamp(spacing, WORKSPACE_MIN_SPACING * scaleFactor,
            WORKSPACE_MAX_SPACING * scaleFactor);
    }

    _getWorkspaceModeForOverviewState(state) {
        const { ControlsState } = OverviewControls;

        switch (state) {
        case ControlsState.HIDDEN:
            return 0;
        case ControlsState.WINDOW_PICKER:
            return 1;
        case ControlsState.APP_GRID:
            return 0;
        }

        return 0;
    }

    _updateWorkspacesState() {
        const adj = this._scrollAdjustment;
        const fitMode = this._fitModeAdjustment.value;

        const { initialState, finalState, progress } =
            this._overviewAdjustment.getStateTransitionParams();

        const workspaceMode = (1 - fitMode) * Util.lerp(
            this._getWorkspaceModeForOverviewState(initialState),
            this._getWorkspaceModeForOverviewState(finalState),
            progress);

        // Fade and scale inactive workspaces
        this._workspaces.forEach((w, index) => {
            w.stateAdjustment.value = workspaceMode;

            const distanceToCurrentWorkspace = Math.abs(adj.value - index);

            const scaleProgress = 1 - Math.clamp(distanceToCurrentWorkspace, 0, 1);

            const scale = Util.lerp(WORKSPACE_INACTIVE_SCALE, 1, scaleProgress);
            w.set_scale(scale, scale);
        });
    }

    _getFitModeForState(state) {
        const { ControlsState } = OverviewControls;

        switch (state) {
        case ControlsState.HIDDEN:
        case ControlsState.WINDOW_PICKER:
            return FitMode.SINGLE;
        case ControlsState.APP_GRID:
            return FitMode.ALL;
        default:
            return FitMode.SINGLE;
        }
    }

    _getInitialBoxes(box) {
        const offsetBox = new Clutter.ActorBox();
        offsetBox.set_size(...box.get_size());

        let fitSingleBox = offsetBox;
        let fitAllBox = offsetBox;

        const { transitioning, initialState, finalState } =
            this._overviewAdjustment.getStateTransitionParams();

        const isPrimary = Main.layoutManager.primaryIndex === this._monitorIndex;

        if (isPrimary && transitioning) {
            const initialFitMode = this._getFitModeForState(initialState);
            const finalFitMode = this._getFitModeForState(finalState);

            // Only use the relative boxes when the overview is in a state
            // transition, and the corresponding fit modes are different.
            if (initialFitMode !== finalFitMode) {
                const initialBox =
                    this._controls.getWorkspacesBoxForState(initialState).copy();
                const finalBox =
                    this._controls.getWorkspacesBoxForState(finalState).copy();

                // Boxes are relative to ControlsManager, transform them;
                //   this.apply_relative_transform_to_point(controls,
                //       new Graphene.Point3D());
                // would be more correct, but also more expensive
                const [parentOffsetX, parentOffsetY] =
                    this.get_parent().allocation.get_origin();
                [initialBox, finalBox].forEach(b => {
                    b.set_origin(b.x1 - parentOffsetX, b.y1 - parentOffsetY);
                });

                if (initialFitMode === FitMode.SINGLE)
                    [fitSingleBox, fitAllBox] = [initialBox, finalBox];
                else
                    [fitAllBox, fitSingleBox] = [initialBox, finalBox];
            }
        }

        return [fitSingleBox, fitAllBox];
    }

    _updateWorkspaceMode() {
        this._updateWorkspacesState();
    }

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

        if (this.get_n_children() === 0)
            return;

        const vertical = global.workspaceManager.layout_rows === -1;
        const rtl = this.text_direction === Clutter.TextDirection.RTL;

        const fitMode = this._fitModeAdjustment.value;

        let [fitSingleBox, fitAllBox] = this._getInitialBoxes(box);
        const fitSingleSpacing =
            this._getSpacing(fitSingleBox, FitMode.SINGLE, vertical);
        fitSingleBox =
            this._getFirstFitSingleWorkspaceBox(fitSingleBox, fitSingleSpacing, vertical);

        const fitAllSpacing =
            this._getSpacing(fitAllBox, FitMode.ALL, vertical);
        fitAllBox =
            this._getFirstFitAllWorkspaceBox(fitAllBox, fitAllSpacing, vertical);

        // Account for RTL locales by reversing the list
        const workspaces = this._workspaces.slice();
        if (rtl)
            workspaces.reverse();

        const [fitSingleX1, fitSingleY1] = fitSingleBox.get_origin();
        const [fitSingleWidth, fitSingleHeight] = fitSingleBox.get_size();
        const [fitAllX1, fitAllY1] = fitAllBox.get_origin();
        const [fitAllWidth, fitAllHeight] = fitAllBox.get_size();

        workspaces.forEach(child => {
            if (fitMode === FitMode.SINGLE)
                box = fitSingleBox;
            else if (fitMode === FitMode.ALL)
                box = fitAllBox;
            else
                box = fitSingleBox.interpolate(fitAllBox, fitMode);

            child.allocate_align_fill(box, 0.5, 0.5, false, false);

            if (vertical) {
                fitSingleBox.set_origin(
                    fitSingleX1,
                    fitSingleBox.y1 + fitSingleHeight + fitSingleSpacing);
                fitAllBox.set_origin(
                    fitAllX1,
                    fitAllBox.y1 + fitAllHeight + fitAllSpacing);
            } else {
                fitSingleBox.set_origin(
                    fitSingleBox.x1 + fitSingleWidth + fitSingleSpacing,
                    fitSingleY1);
                fitAllBox.set_origin(
                    fitAllBox.x1 + fitAllWidth + fitAllSpacing,
                    fitAllY1);
            }
        });
    }

    getActiveWorkspace() {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();
        return this._workspaces[active];
    }

    prepareToLeaveOverview() {
        for (let w = 0; w < this._workspaces.length; w++)
            this._workspaces[w].prepareToLeaveOverview();
    }

    syncStacking(stackIndices) {
        for (let i = 0; i < this._workspaces.length; i++)
            this._workspaces[i].syncStacking(stackIndices);
    }

    _scrollToActive() {
        const { workspaceManager } = global;
        const active = workspaceManager.get_active_workspace_index();

        this._animating = true;
        this._updateVisibility();

        this._scrollAdjustment.remove_transition('value');
        this._scrollAdjustment.ease(active, {
            duration: WORKSPACE_SWITCH_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            onComplete: () => {
                this._animating = false;
                this._updateVisibility();
            },
        });
    }

    _updateVisibility() {
        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();

        const fitMode = this._fitModeAdjustment.value;
        const singleFitMode = fitMode === FitMode.SINGLE;

        for (let w = 0; w < this._workspaces.length; w++) {
            let workspace = this._workspaces[w];

            if (this._animating || this._gestureActive || !singleFitMode)
                workspace.show();
            else
                workspace.visible = Math.abs(w - active) <= 1;
        }
    }

    _updateWorkspaces() {
        let workspaceManager = global.workspace_manager;
        let newNumWorkspaces = workspaceManager.n_workspaces;

        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._overviewAdjustment);
                this.add_actor(workspace);
                this._workspaces[j] = workspace;
            } else  {
                workspace = this._workspaces[j];

                if (workspace.metaWorkspace != metaWorkspace) { /* removed */
                    workspace.destroy();
                    this._workspaces.splice(j, 1);
                } /* else kept */
            }
        }

        for (let j = this._workspaces.length - 1; j >= newNumWorkspaces; j--) {
            this._workspaces[j].destroy();
            this._workspaces.splice(j, 1);
        }

        this._updateWorkspacesState();
    }

    _activeWorkspaceChanged(_wm, _from, _to, _direction) {
        this._scrollToActive();
    }

    _onDestroy() {
        super._onDestroy();

        this._workspaces = [];
    }

    startTouchGesture() {
        this._gestureActive = true;

        this._updateVisibility();
    }

    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
    _onScrollAdjustmentChanged() {
        if (!this.has_allocation())
            return;

        const adj = this._scrollAdjustment;
        const allowSwitch =
            adj.get_transition('value') === null && !this._gestureActive;

        let workspaceManager = global.workspace_manager;
        let active = workspaceManager.get_active_workspace_index();
        let current = Math.round(adj.value);

        if (allowSwitch && active !== current) {
            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());
        }

        this._updateWorkspacesState();
        this.queue_relayout();
    }
});

var ExtraWorkspaceView = GObject.registerClass(
class ExtraWorkspaceView extends WorkspacesViewBase {
    _init(monitorIndex, overviewAdjustment) {
        super._init(monitorIndex, overviewAdjustment);
        this._workspace =
            new Workspace.Workspace(null, monitorIndex, overviewAdjustment);
        this.add_actor(this._workspace);
    }

    _updateWorkspaceMode() {
        const overviewState = this._overviewAdjustment.value;

        const progress = Math.clamp(overviewState,
            OverviewControls.ControlsState.HIDDEN,
            OverviewControls.ControlsState.WINDOW_PICKER);

        this._workspace.stateAdjustment.value = progress;
    }

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

        const [width, height] = box.get_size();
        const [, childWidth] = this._workspace.get_preferred_width(height);

        const childBox = new Clutter.ActorBox();
        childBox.set_origin(Math.round((width - childWidth) / 2), 0);
        childBox.set_size(childWidth, height);
        this._workspace.allocate(childBox);
    }

    getActiveWorkspace() {
        return this._workspace;
    }

    prepareToLeaveOverview() {
        this._workspace.prepareToLeaveOverview();
    }

    syncStacking(stackIndices) {
        this._workspace.syncStacking(stackIndices);
    }

    startTouchGesture() {
    }

    endTouchGesture() {
    }
});

const SecondaryMonitorDisplay = GObject.registerClass(
class SecondaryMonitorDisplay extends St.Widget {
    _init(monitorIndex, controls, scrollAdjustment, fitModeAdjustment, overviewAdjustment) {
        this._monitorIndex = monitorIndex;
        this._controls = controls;
        this._scrollAdjustment = scrollAdjustment;
        this._fitModeAdjustment = fitModeAdjustment;
        this._overviewAdjustment = overviewAdjustment;

        super._init({
            style_class: 'secondary-monitor-workspaces',
            constraints: new Layout.MonitorConstraint({
                index: this._monitorIndex,
                work_area: true,
            }),
            clip_to_allocation: true,
        });

        this.connect('destroy', () => this._onDestroy());

        this._thumbnails = new ThumbnailsBox(
            this._scrollAdjustment, monitorIndex);
        this.add_child(this._thumbnails);

        this._thumbnails.connect('notify::should-show',
            () => this._updateThumbnailVisibility());

        this._overviewAdjustment.connectObject('notify::value', () => {
            this._updateThumbnailParams();
            this.queue_relayout();
        }, this);

        this._settings = new Gio.Settings({ schema_id: MUTTER_SCHEMA });
        this._settings.connect('changed::workspaces-only-on-primary',
            () => this._workspacesOnPrimaryChanged());
        this._workspacesOnPrimaryChanged();
    }

    _getThumbnailParamsForState(state) {
        const { ControlsState } = OverviewControls;

        let opacity, scale;
        switch (state) {
        case ControlsState.HIDDEN:
        case ControlsState.WINDOW_PICKER:
            opacity = 255;
            scale = 1;
            break;
        case ControlsState.APP_GRID:
            opacity = 0;
            scale = 0.5;
            break;
        default:
            opacity = 255;
            scale = 1;
            break;
        }

        return { opacity, scale };
    }

    _getThumbnailsHeight(box) {
        if (!this._thumbnails.visible)
            return 0;

        const [width, height] = box.get_size();
        const { expandFraction } = this._thumbnails;
        const [thumbnailsHeight] = this._thumbnails.get_preferred_height(width);
        return Math.min(
            thumbnailsHeight * expandFraction,
            height * MAX_THUMBNAIL_SCALE);
    }

    _getWorkspacesBoxForState(state, box, padding, thumbnailsHeight, spacing) {
        const { ControlsState } = OverviewControls;
        const workspaceBox = box.copy();
        const [width, height] = workspaceBox.get_size();

        switch (state) {
        case ControlsState.HIDDEN:
            break;
        case ControlsState.WINDOW_PICKER:
            workspaceBox.set_origin(0, padding + thumbnailsHeight + spacing);
            workspaceBox.set_size(
                width,
                height - 2 * padding - thumbnailsHeight - spacing);
            break;
        case ControlsState.APP_GRID:
            workspaceBox.set_origin(0, padding);
            workspaceBox.set_size(
                width,
                height - 2 * padding);
            break;
        }

        return workspaceBox;
    }

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

        const themeNode = this.get_theme_node();
        const contentBox = themeNode.get_content_box(box);
        const [width, height] = contentBox.get_size();
        const { expandFraction } = this._thumbnails;
        const spacing = themeNode.get_length('spacing') * expandFraction;
        const padding =
            Math.round((1 - SECONDARY_WORKSPACE_SCALE) * height / 2);

        const thumbnailsHeight = this._getThumbnailsHeight(contentBox);

        if (this._thumbnails.visible) {
            const childBox = new Clutter.ActorBox();
            childBox.set_origin(0, padding);
            childBox.set_size(width, thumbnailsHeight);
            this._thumbnails.allocate(childBox);
        }

        const {
            currentState, initialState, finalState, transitioning, progress,
        } = this._overviewAdjustment.getStateTransitionParams();

        let workspacesBox;
        const workspaceParams = [contentBox, padding, thumbnailsHeight, spacing];
        if (!transitioning) {
            workspacesBox =
                this._getWorkspacesBoxForState(currentState, ...workspaceParams);
        } else {
            const initialBox =
                this._getWorkspacesBoxForState(initialState, ...workspaceParams);
            const finalBox =
                this._getWorkspacesBoxForState(finalState, ...workspaceParams);
            workspacesBox = initialBox.interpolate(finalBox, progress);
        }
        this._workspacesView.allocate(workspacesBox);
    }

    _onDestroy() {
        if (this._settings)
            this._settings.run_dispose();
        this._settings = null;
    }

    _workspacesOnPrimaryChanged() {
        this._updateWorkspacesView();
        this._updateThumbnailVisibility();
    }

    _updateWorkspacesView() {
        if (this._workspacesView)
            this._workspacesView.destroy();

        if (this._settings.get_boolean('workspaces-only-on-primary')) {
            this._workspacesView = new ExtraWorkspaceView(
                this._monitorIndex,
                this._overviewAdjustment);
        } else {
            this._workspacesView = new WorkspacesView(
                this._monitorIndex,
                this._controls,
                this._scrollAdjustment,
                this._fitModeAdjustment,
                this._overviewAdjustment);
        }
        this.add_child(this._workspacesView);
    }

    _updateThumbnailVisibility() {
        const visible =
            this._thumbnails.should_show &&
            !this._settings.get_boolean('workspaces-only-on-primary');

        if (this._thumbnails.visible === visible)
            return;

        this._thumbnails.show();
        this._updateThumbnailParams();
        this._thumbnails.ease_property('expand-fraction', visible ? 1 : 0, {
            duration: OverviewControls.SIDE_CONTROLS_ANIMATION_TIME,
            mode: Clutter.AnimationMode.EASE_OUT_QUAD,
            onComplete: () => (this._thumbnails.visible = visible),
        });
    }

    _updateThumbnailParams() {
        if (!this._thumbnails.visible)
            return;

        const { initialState, finalState, progress } =
            this._overviewAdjustment.getStateTransitionParams();

        const initialParams = this._getThumbnailParamsForState(initialState);
        const finalParams = this._getThumbnailParamsForState(finalState);

        const opacity =
            Util.lerp(initialParams.opacity, finalParams.opacity, progress);
        const scale =
            Util.lerp(initialParams.scale, finalParams.scale, progress);

        this._thumbnails.set({
            opacity,
            scale_x: scale,
            scale_y: scale,
        });
    }

    getActiveWorkspace() {
        return this._workspacesView.getActiveWorkspace();
    }

    prepareToLeaveOverview() {
        this._workspacesView.prepareToLeaveOverview();
    }

    syncStacking(stackIndices) {
        this._workspacesView.syncStacking(stackIndices);
    }

    startTouchGesture() {
        this._workspacesView.startTouchGesture();
    }

    endTouchGesture() {
        this._workspacesView.endTouchGesture();
    }
});

var WorkspacesDisplay = GObject.registerClass(
class WorkspacesDisplay extends St.Widget {
    _init(controls, scrollAdjustment, overviewAdjustment) {
        super._init({
            layout_manager: new Clutter.BinLayout(),
            reactive: true,
        });

        this._controls = controls;
        this._overviewAdjustment = overviewAdjustment;
        this._fitModeAdjustment = new St.Adjustment({
            actor: this,
            value: FitMode.SINGLE,
            lower: FitMode.SINGLE,
            upper: FitMode.ALL,
        });

        let workspaceManager = global.workspace_manager;
        this._scrollAdjustment = scrollAdjustment;

        global.window_manager.connectObject('switch-workspace',
            this._activeWorkspaceChanged.bind(this), this);

        this._swipeTracker = new SwipeTracker.SwipeTracker(
            Main.layoutManager.overviewGroup,
            Clutter.Orientation.HORIZONTAL,
            Shell.ActionMode.OVERVIEW,
            { allowDrag: false });
        this._swipeTracker.allowLongSwipes = true;
        this._swipeTracker.connect('begin', this._switchWorkspaceBegin.bind(this));
        this._swipeTracker.connect('update', this._switchWorkspaceUpdate.bind(this));
        this._swipeTracker.connect('end', this._switchWorkspaceEnd.bind(this));
        this.connect('notify::mapped', this._updateSwipeTracker.bind(this));

        workspaceManager.connectObject(
            'workspaces-reordered', this._workspacesReordered.bind(this),
            'notify::layout-rows', this._updateTrackerOrientation.bind(this), this);
        this._updateTrackerOrientation();

        Main.overview.connectObject(
            'window-drag-begin', this._windowDragBegin.bind(this),
            'window-drag-end', this._windowDragEnd.bind(this), this);

        this._primaryVisible = true;
        this._primaryIndex = Main.layoutManager.primaryIndex;
        this._workspacesViews = [];

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

        this._inWindowDrag = false;
        this._leavingOverview = false;

        this._gestureActive = false; // touch(pad) gestures

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

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

    _windowDragBegin() {
        this._inWindowDrag = true;
        this._updateSwipeTracker();
    }

    _windowDragEnd() {
        this._inWindowDrag = false;
        this._updateSwipeTracker();
    }

    _updateSwipeTracker() {
        this._swipeTracker.enabled =
            this.mapped &&
            !this._inWindowDrag &&
            !this._leavingOverview;
    }

    _workspacesReordered() {
        let workspaceManager = global.workspace_manager;

        this._scrollAdjustment.value =
            workspaceManager.get_active_workspace_index();
    }

    _activeWorkspaceChanged(_wm, _from, to, _direction) {
        if (this._gestureActive)
            return;

        this._scrollAdjustment.ease(to, {
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            duration: WORKSPACE_SWITCH_TIME,
        });
    }

    _updateTrackerOrientation() {
        const { layoutRows } = global.workspace_manager;
        this._swipeTracker.orientation = layoutRows !== -1
            ? Clutter.Orientation.HORIZONTAL
            : Clutter.Orientation.VERTICAL;
    }

    _directionForProgress(progress) {
        if (global.workspace_manager.layout_rows === -1) {
            return progress > 0
                ? Meta.MotionDirection.DOWN
                : Meta.MotionDirection.UP;
        } else if (this.text_direction === Clutter.TextDirection.RTL) {
            return progress > 0
                ? Meta.MotionDirection.LEFT
                : Meta.MotionDirection.RIGHT;
        } else {
            return progress > 0
                ? Meta.MotionDirection.RIGHT
                : Meta.MotionDirection.LEFT;
        }
    }

    _switchWorkspaceBegin(tracker, monitor) {
        if (this._workspacesOnlyOnPrimary && monitor !== this._primaryIndex)
            return;

        let workspaceManager = global.workspace_manager;
        let adjustment = this._scrollAdjustment;
        if (this._gestureActive)
            adjustment.remove_transition('value');

        const distance = global.workspace_manager.layout_rows === -1
            ? this.height : this.width;

        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].startTouchGesture();

        let progress = adjustment.value / adjustment.page_size;
        let points = Array.from(
            { length: workspaceManager.n_workspaces }, (v, i) => i);

        tracker.confirmSwipe(distance, points, progress, Math.round(progress));

        this._gestureActive = true;
    }

    _switchWorkspaceUpdate(tracker, progress) {
        let adjustment = this._scrollAdjustment;
        adjustment.value = progress * adjustment.page_size;
    }

    _switchWorkspaceEnd(tracker, duration, endProgress) {
        let workspaceManager = global.workspace_manager;
        let newWs = workspaceManager.get_workspace_by_index(endProgress);

        this._scrollAdjustment.ease(endProgress, {
            mode: Clutter.AnimationMode.EASE_OUT_CUBIC,
            duration,
            onComplete: () => {
                if (!newWs.active)
                    newWs.activate(global.get_current_time());
                this._endTouchGesture();
            },
        });
    }

    _endTouchGesture() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].endTouchGesture();
        this._gestureActive = false;
    }

    vfunc_navigate_focus(from, direction) {
        return this._getPrimaryView()?.navigate_focus(from, direction, false);
    }

    setPrimaryWorkspaceVisible(visible) {
        if (this._primaryVisible === visible)
            return;

        this._primaryVisible = visible;

        const primaryIndex = Main.layoutManager.primaryIndex;
        const primaryWorkspace = this._workspacesViews[primaryIndex];
        if (primaryWorkspace)
            primaryWorkspace.visible = visible;
    }

    prepareToEnterOverview() {
        this.show();
        this._updateWorkspacesViews();

        Main.overview.connectObject(
            'windows-restacked', this._onRestacked.bind(this),
            'scroll-event', this._onScrollEvent.bind(this), this);

        global.stage.connectObject(
            'key-press-event', this._onKeyPressEvent.bind(this), this);
    }

    prepareToLeaveOverview() {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].prepareToLeaveOverview();

        this._leavingOverview = true;
        this._updateSwipeTracker();
    }

    vfunc_hide() {
        Main.overview.disconnectObject(this);
        global.stage.disconnectObject(this);

        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].destroy();
        this._workspacesViews = [];

        this._leavingOverview = false;

        super.vfunc_hide();
    }

    _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 (i === this._primaryIndex) {
                view = new WorkspacesView(i,
                    this._controls,
                    this._scrollAdjustment,
                    this._fitModeAdjustment,
                    this._overviewAdjustment);

                view.visible = this._primaryVisible;
                this.bind_property('opacity', view, 'opacity', GObject.BindingFlags.SYNC_CREATE);
                this.add_child(view);
            } else {
                view = new SecondaryMonitorDisplay(i,
                    this._controls,
                    this._scrollAdjustment,
                    this._fitModeAdjustment,
                    this._overviewAdjustment);
                Main.layoutManager.overviewGroup.add_actor(view);
            }

            this._workspacesViews.push(view);
        }
    }

    _getMonitorIndexForEvent(event) {
        let [x, y] = event.get_coords();
        let rect = new Meta.Rectangle({ x, 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() {
        const primaryView = this._getPrimaryView();
        return primaryView
            ? primaryView.getActiveWorkspace().hasMaximizedWindows()
            : false;
    }

    _onRestacked(overview, stackIndices) {
        for (let i = 0; i < this._workspacesViews.length; i++)
            this._workspacesViews[i].syncStacking(stackIndices);
    }

    _onScrollEvent(actor, event) {
        if (this._swipeTracker.canHandleScrollEvent(event))
            return Clutter.EVENT_PROPAGATE;

        if (!this.mapped)
            return Clutter.EVENT_PROPAGATE;

        if (this._workspacesOnlyOnPrimary &&
            this._getMonitorIndexForEvent(event) != this._primaryIndex)
            return Clutter.EVENT_PROPAGATE;

        return Main.wm.handleWorkspaceScroll(event);
    }

    _onKeyPressEvent(actor, event) {
        const { ControlsState } = OverviewControls;
        if (this._overviewAdjustment.value !== ControlsState.WINDOW_PICKER)
            return Clutter.EVENT_PROPAGATE;

        if (!this.reactive)
            return Clutter.EVENT_PROPAGATE;

        const { workspaceManager } = global;
        const vertical = workspaceManager.layout_rows === -1;
        const rtl = this.get_text_direction() === Clutter.TextDirection.RTL;

        let which;
        switch (event.get_key_symbol()) {
        case Clutter.KEY_Page_Up:
            if (vertical)
                which = Meta.MotionDirection.UP;
            else if (rtl)
                which = Meta.MotionDirection.RIGHT;
            else
                which = Meta.MotionDirection.LEFT;
            break;
        case Clutter.KEY_Page_Down:
            if (vertical)
                which = Meta.MotionDirection.DOWN;
            else if (rtl)
                which = Meta.MotionDirection.LEFT;
            else
                which = Meta.MotionDirection.RIGHT;
            break;
        case Clutter.KEY_Home:
            which = 0;
            break;
        case Clutter.KEY_End:
            which = workspaceManager.n_workspaces - 1;
            break;
        default:
            return Clutter.EVENT_PROPAGATE;
        }

        let ws;
        if (which < 0)
            // Negative workspace numbers are directions
            // with respect to the current workspace
            ws = workspaceManager.get_active_workspace().get_neighbor(which);
        else
            // Otherwise it is a workspace index
            ws = workspaceManager.get_workspace_by_index(which);

        if (ws)
            Main.wm.actionMoveWorkspace(ws);

        return Clutter.EVENT_STOP;
    }

    get _workspacesOnlyOnPrimary() {
        return this._settings.get_boolean('workspaces-only-on-primary');
    }

    get fitModeAdjustment() {
        return this._fitModeAdjustment;
    }
});
(uuay);��������8h��P�4��!�4��!�5�h�5�|�5���5���5��6��86��H6��X6�h6�x6�0�6�D�6�X�6�t�6��h7���7��(:�,h<�p�<��=��X=��X>�T�>�p(?���?��x@�(8A�`B��B��xB��C��C�L(D�t�D��E���E�HF�8�G�XH���H��(I���I�D�K��L��XL��xL���L��XM�0(O���P��(Q�R��R�4�T�t�U��hV���V���Y�L �Z�� �Z�� 8[�� �[�� \�!�\� !�]�\!X^�|!�^��!�^��!(_�"8_�"X_�0"`�\"�`�|"�a��"c�#�c�<#�d�l#�d��#�d��#he��#�e�$$hf�h$�f��$Hg��$�g�$%Hh�d%�h��%Xi��%�i�&�i�&�i�8&Hj�X&�j�t&l��&�l��&m�P'�m��'Hn�(�n�l(Ho��(p� )hp�@)Hs��)�t��)�u�*�v�d*xw��*x��*y�+�y�8+H|��+�|��+}��+H}��+X}�,�}�8,�}�L,~�x,�~��,���,8��-ȁ�T-����-H���-x���-����-؃�.��$.8��@.���\.Ȅ�x.(���.h���.���/h��(/؇�P/8��l/Ȉ��/8��0����0����0؊�1H��P1ȍ��1���D2��d2ȑ��2���2H��<3ȕ��3����3���3X��<48��p4����4���4h��5Ȝ�@5(��h5X���5����5���5����5h�� 6ء�<6h��h6Ȣ��6���6h���6���,7��X7h���7����7h���7���8���8h��P8����8����8Ȫ�9���9���X9���t98���9���9(��:x���:����:X��;x��;د�8;���t;(���;h���;���;��� <ز�4<H��X<���<H���<����<8��=��8=���d=���=H���=����=h��L>X���>H��?h��?���8?���|?����?����?���?��@@���@x���@(��,A���TA���tA����A8��B���XBH��xBh���B����B����B����B���C��$C8���C���Cx���C���DX��$D���HDX���D����DH���D���E��E���PE���lE����E���Ex��F���FH��@F���dF����F����F��� Gx��lG(���G����G��DH���lHx���HH���H��,Ih��dIh���I��� J���4J����J����J(��@K���xK8���K��$LX��HLx���L8���L���(MX��`Mx��xM����M���M��LN8��N��hO��|O���O��O(��O8��OH��Oh��Ox�P��P��0P��DP��XP��lP��P(��PX��P���P���PH	�Q�	�LQ�
��Q���Q
�RX
�<R8�pR8��RX��R��S�HS��tS���SH��S���S��TX�4T�lTh��T���TH��T�(U��XU���Uh��U(�(V��TVX�|V���V(�DW��tW�!��W�"�X#�,X�$��X(%��Xh&��X�&�Y�&�,Y('�XY�'��Y+�Z�+�4Z,�\Z�,��Z�,��Zx-��Z�-�[�.�@[0��[�1��[�2�L\4�x\(5��\x5��\�5�]�5� ]�5�4]�6�p]x7��]�7��]�7�^8� ^�8�T^�8�h^�8�|^�8��^�:��^8;�_H;�,_�;�L_h<��_�<��_�<��_�<��_�<��_�<��_H=�`>�X`h>��`�>��`�?��`�?�a�?�a@�(ah@�HaA��aB��a�B�(bXC�Pb�C�pb�C��bXD��bxD��b�D��bXE�$chF�`cxG��c�G��c�G��cH��cxH�d�H�,d�J��d�L��d�M�PeXN�peO��e�O��e�O��e�O�f�O�f�O�(fXP�Hf�P�`fQ��f(Q��f8R��f�R�gS� ghS�Hg�S�pg8T��g�T�hU�h8U�8h�U�ph(W��hHW��h�Y�i[�hi�[��iH\��i�]�0j�^�lj�^��jH_��j�_��j`��jh`�k�a�lk�b��kHe�hlhg�m�h�lm�i��m(k�Tn�l��n�m�<o8o��o�p�p�q�\p�q�tp(r��p�s��p(t�qHt�$qu�pqxu��q�u��q�v�rx�Dr�z��rh{�s�{�(s(|�Hs�|�hs�|��s�}��s���|t���t؂�u���`u���u(���u؄��u���uX��v���,v���LvX��lv����vh���v(��w8��4w���Tw��tw����w����w؍��w(��xx��|x���x���x���LyX���yx���y(��0zx��Xz���lz����zؚ��z���{���`{H���{X���{h���{����{ȝ��{؝�|X��8|h��L|؟��|����|���|ȡ�}��L}����}����}��4~���T~ȧ�p~����~���~��8��(���Hت�h(���x�������������X��4�Ȯ���خ�������x��؀X���x��l�ش���x��܂�����d�X��������x��ă����ȼ�@�8��t����܄���<�����X��؅(������,�8������܆h�������h����(���H��H�(���������x���H��L�X��`������H��������8���h��0����|������(����h����������8�8��d�x�����������ċh���H��@�������������0����L�X��l������������@�X��l���������X������P������(��ԏ����X��(������8����h�������А���(���8���H��,�X��@�h��T�x��h����|�������������������̑���8����������$����8����L���h�8��|�X����������������ܒh�����������4���������(��,�X������ؔ���(���������������H���t����������x�����$�8��D���|�X����x��̗������,�H�T�(�������h�T���t�(
�ЙH����X�����H�Ԛ����X�T�x�l�x�̛������8�8���(�����(�`�����(��X�(���P�x�����Ğ��ܞ�����(�X�T�(���h�������� �4��&���H'�Ġ�'���'���(�@��,���zRx�$���`6FJw�?:*3$"D���p\��P6t(��$�� ���&�8��4�&�P�L�H�(D�<@�P<�d8�E�P�<�E�P8�@��J�A�A �\
CBCAFBG������qDE
G@���3B�B�A ��
BBCY
EBHlEE@8�!�3B�B�A ��
BBCY
EBHlEE|�#�ADPO ]� $�ADPO ]�P$�ADPO ]���$��F�B�B �A(�D0�I8I@UHRPBXB`L0I8I@MHMPCXB`BhBpI8I@OHKPAXB`BhBpP0X(D EBB`�$�QE�r
I4|@%�iF�E�K �Y(M0c(D ABB0�x%��F�A�D i
DBFDABH��%��F�E�A �A(�D0|
(D ABBJT(A ABB44H&��F�B�A ��(O0F(D ABB<l�&��F�E�A �A(�D0�8Y@P(D ABB�`'�$�\'�XB�PN qDB4��'��E�A�G0A
AAHP8]@R8A04 �'��E�A�G0A
AAHP8]@R8A0$Xd(�eF�J�N yDE$��(�UE�A�G AAA$��(�E�A�G gAAH�<)��B�E�B �D(�D0�Y
(D EBBIA(A BBB$�)��E�pJ \AAD*�lE�M
F$dX+�CA�XJ ]AA(��+��F�D�G0�
DBE0�$,�NE�D�G T
FAEVFA`�@,��F�B�B �E(�D0�A8�G@{
8A0A(B BBBHt
8F0A(E BBBB8P�,��F�B�A �A(�D0[
(D ABBF� .�]|�l.�NH @
A��.���.�<E�i
BKH��.��B�G�E �K(�A0�g
(C BBBFq(D GIB`< /��F�B�E �E(�K0�G8�GP`
8A0A(B BBBK
8K0A(B BBBG@��0��B�D�E �A(�A0�G�	\
0A(A BBBH$��1�CA�XJ ]AA	 2��E��(	�2�}\\<@	\3�SB�B�D �C(�G�
(D ABBI(�	|5��E�A�G O
AAB,�	6��F�A�D ��
ABD$�	�6�^B�XJ sDBP
�6��F�B�B �B(�K0�A8�DPXG`k8A0A(B BBBKP0X
D9��F�E�D �G0�
 ABBF�
:�,E�Q
JG�
 :�oa�[
Dn�
p:�oa�[
Dn�
�:�oa�[
Dn;�oa�[
Dn8,`;�\F�A�D �X(Z0t(A H
ABKh�<�oa�[
Dn��<�oa�[
Dn�$�����<�$��<�FF�A�G jDE =�	 =�F�T(< =��F�D�D 
ABHh�=��E��
G<�T>��b�A�N T
ABHP(H0u(A KABL��>��F�E�L �B(�A0�D8�G�O
8A0A(B BBBA,
@��B�K0a8L@d8A0T
EA,H
�@��B�K0a8L@d8A0T
EAx
$A�#E�X�
8A�+F�TD�
LA�dF�N�E �A(�D0�]8G@X8A0A(A BBB4�
tA�iF�E�D �D(�D0K(A ABB@0�A��F�N�A �D(�G0\8F@Y8A0m(A ABB<t�A�eF�N�A �D(�G0\8G@[(A ABB4�(B�iF�E�D �D(�D0K(A ABB@�`B��F�N�A �D(�G0\8F@Y8A0m(A ABB<0�B�eF�N�A �D(�G0\8G@[(A ABB8p�B�kF�E�E �D(�D0�H(A EBB@�C��F�G�A �K(�G0\8I@Y8A0p(A ABB�lC�"E�U�C�"E�U(�C�$E�WD�C�Ya�[
DXd�C�<E�qH�D��F�E�I �B(�D0�D8�D`)
8A0A(B BBBK$�PE�iF�D�G QDBd��E��F�E�E �E(�D0�D8�F@GHZPHXI`AhIpAxH�A�C�Q8A0A(B BBB4\�E�hF�E�D �|
EEKADBx��E��F�E�E �E(�D0�D8�F@GH[POXI`AhIpAxD�I@Q
8D0D(B BBBJD8C0A(B BBBd<F��F�E�E �E(�D0�D8�F@GHZPHXI`AhIpAxH�A�C�Q8A0A(B BBB4xdF�hF�E�D �|
EEKADBx��F��F�E�E �E(�D0�D8�F@GH[POXI`AhIpAxD�I@Q
8D0D(B BBBJD8C0A(B BBB,�F�Ya�[
DXLL G��F�B�B �B(�A0�D8�G�
8A0A(B BBBJ@��I�>F�B�E �A(�D0�DP�
0D(A BBBH@��J�>F�B�E �D(�A0�DP�
0A(A BBBFH$�K�F�B�B �A(�J�u�H�[�U
(A BBBA$plL��E�D�D �AA4��L��F�E�D �C(�D0a(A AFB<�<M��F�E�E �D(�D0�q
(D BBBM0�M��E�D�D v
EAGxEA\D�N�QF�B�B �B(�A0�D8�G���H�Y�A�i
8C0A(B BBBG��P�jE�Q
JE$��P�UF�D�D @DB�0Q�*E�]DQ�$@Q�FF�A�G jDEDhQ�	(XdQ�IF�H�D �sAB(��Q��F�D�D 
ABH�R��E��
G<��R��b�A�N T
ABHP(H0u(A KABL,S��F�E�L �B(�A0�D8�G�O
8A0A(B BBBA,`lT��B�K0a8L@d8A0T
EA,��T��B�K0a8L@d8A0T
EA��U�#E�X��U�+F�T��U�#E�X�U�+F�T0�U�#E�XL�U�VH H
Ah4V�+F�T�HV�Ya�[
DX��V�<E�qH��V��F�E�I �B(�D0�D8�D`)
8A0A(B BBBK$�W�iF�D�G QDB$48X�iF�D�G QDB\�X�QF�Jdx�X��F�E�E �E(�D0�D8�F@GHZPHXI`AhIpAxH�A�C�Q8A0A(B BBB4��X�hF�E�D �|
EEKADBx$Y��F�E�E �E(�D0�D8�F@GH[POXI`AhIpAxD�I@Q
8D0D(B BBBJD8C0A(B BBBd�hY��F�E�E �E(�D0�D8�F@GHZPHXI`AhIpAxH�A�C�Q8A0A(B BBB$��Y�NE�\J `AA4$�Y�hF�E�D �|
EEKADBt\�Y�tF�E�B �B(�A0�D8�DP�X[`IhBpBxB�IP`
8A0A(B BBBIYX[`IhBpBxB�IPx��[��F�E�E �E(�D0�D8�F@GH[POXI`AhIpAxD�I@Q
8D0D(B BBBJD8C0A(B BBBP<\�Ya�[
DXLp|\��F�B�B �B(�A0�D8�G�
8A0A(B BBBJ@�_�>F�B�E �A(�D0�DP�
0D(A BBBH@`�>F�B�E �D(�A0�DP�
0A(A BBBF\Ha�rF�I�B �B(�A0�D8�N���H�Z�A�m
8A0A(B BBBA$�$b��E�D�D �AA(��b��F�D�D �nABH� c�<F�B�E �E(�D0�D8�HP�
8D0A(B BBBL0Hd��E�D�D v
EAGxEA\|�d�QF�B�B �B(�A0�D8�G���H�Y�A�i
8C0A(B BBBG��f�jE�Q
JE$�g�UF�D�D @DB$$Hg�YF�D�D DDB$L�g�YF�D�D DDBt�g�*E�]��g�Ya�[
DX�h�Ya�[
DX,�Lh��E�bH L(K0I
X( �i�fF�D�G0�
ABC, @k�aE�n
M(H �k��F�A�G 
DEJ$t �k�RE�D�D AAA� 0l�1E�N
EQ4� Pl�\B�A�A �f
ABChAB@� xl�B�A�D �G`p
 AABFm
 HABH(8!Tm��F�A�D ��AB(d!�m�HF�D�A �lJB4�!�m��E�A�G0h
AAIk
AAK$�!4o�iE�Z
QX
HT�!|o�4E�Z
QC"�o�4$"�o��F�E�A �t
BBDcBBL\"p�F�B�B �B(�G0�o
(B BBBFA
(B BBBGL�"�p�%Z�B�A �A(�G0�
(A ABBID
(F ABBA�"�q�#�q�#<$#�q��E�D�G z
FAGD
CAHXDAd#(r�&F�O(�#<r�rF�A�A �fABX�#�r��F�B�E �D(�D0�s8J@THMPI0A
(A BBBDL(D EBB$�r�<]R� $s�GF�E�D �D(�F0H
(D ABBFG
(D ABBHI
(D CBBLI
(D DBBKA8J@SHMPI0D(A ABB�$�s�	H�$�s��F�B�D �A(�J0`
(A ABBC�(D ABB%Lt�$%Xt�Ya�[
DX8D%�t��E�vH L(K0IOH L(K0IG
G(�%u��E�D�D a
AAH�%�u�9\\(�%�u��F�D�J z
IDE8�%v�|I�A�G uD�E�Q ��DQ�B�,&`v�M @&�v�iE�_
LL
L8d&�v��F�B�A �C(�Dpu
(A ABBA(�&Lw�YF�A�A �MAB�&�w�Ya�[
DX(�&�w��F�D�G c
ABI('$x��F�N�G0q
ABA(D'�x�vE�D�D Q
AAH4p',y�fF�D�D �{
DBAG
AEA�'dy�9\\�'�y�Ya�[
DXt�'�y��
F�B�B �E(�A0�D8�G�
8A0A(B BBBH�
8A0A(B BBBDO�N�Q�A�4X(���F�B�D �D(�D0�(A ABBx�(̄��F�E�D �D(�F08J@THMPI(A ABBF0L
(D ABBKJ
(D ABBEG(D ABB)@��HK$)H��\E�p
K[@D)����F�A�D �o
AHMA
HKLAAB�)��[E�r
IZ<�)D���F�F�G }
DEGD
ABIDQB�)Ԇ�TE�m
F[@*���F�A�D �y
AHKy
HKLAAB|L*Ї��F�E�B �D(�D0�H8J@THMPI0A
(A BBBGI
(D BBBMI
(D DBBKI(D DBB�*P��Ya�[
DXH�*����F�B�A �D(�D0r
(A ABBGD(D ABB$8+��pF�A�G ]AB`+<��JE�w
LA<�+l���F�D�G \
ABHn
AELDQB`�+��pF�B�E �E(�D0�D8�F`�
8A0A(B BBBHT
8Q0A(B BBBE<$,���F�A�G J
ABEO
AEKDQBd,x��fE�{
PQ�,ȋ�HK�,Ћ�Ya�[
DX$�,���E�D�D �AA �,��
E�J��
AF-ԍ�-Ѝ�!\0-��F�B�A �D(�G0|
(A ABBJ�
(A DBBGD
(Q ABBF0�-����F�A�D �D@
 AABC �-H��fE�G L
DE �-���fE�G L
DE .��fE�G L
DE 0.,��fE�G L
DE8T.x���F�E�D �D(�G@L
(J DBBE �.̑�fE�G L
DE0�.��xF�E�D �F0L
 IBBE �.d��fE�G L
DE/���^H L
E0(/��zF�E�D �G0L
 JBBE\/@��^H L
E8x/����F�E�D �D(�G@L
(J DBBE0�/ؓ�zF�E�D �G0L
 JBBE �/$��fE�G L
DE0p��^H L
E (0���fE�G L
DE L0��fE�G L
DEp0L��"E�T4�0`��E�A�D@�
DAKe
CAAd�08���F�B�B �E(�D0�D8�D�r
8A0A(B BBBI�
8D0A(B BBBMH,1����F�B�A �A(�D0N
(D ABBKT(D DBBXx1���F�B�D �D(�D0[
(A ABBK�
(C ABBFB8P@R8A0$�1X��UF�A�G @DBP�1����F�B�A �d(M0o(A ^
DBJQ
BBCT(I0h(A $P2̝��E�D�D �AA@x2T���F�B�D �h
BBHF
BBFX
BBD8�2����F�A�A �T
ABAp
FBH<�2T���E�A�G x
AAIa
AAMjAA483ԟ�_F�E�D �C(�G0x(D ABB`p3����F�B�B �E(�D0�C8�F`chCpOxL�B�A�N`S
8D0A(B BBBBT�3���[F�B�B �E(�D0�G@�
0A(B BBBEX
0I(B BBBO,4���l@4��� F�B�B �A(�D0�
(J DDBLT
(I BBBMD
(L BBBJD(Q BBBL�4L���F�E�B �B(�A0�S
(B BBBEn
(G BBBEH5���oF�B�D �D(�D0a
(A ABBEh(A ABB4L5��rF�A�D �Y
ABI~ABH�5(���F�B�E �A(�A0�Q
(D BBBFK(D BBB\�5l���F�B�B �B(�A0�D
(E BBBDW
(E BBBFD(E BBB 06��;J�\
�JCE�LT6��F�B�B �B(�A0�D8�G�
8C0A(B BBBE@�6ة��F�E�B �A(�C0�Dp�
0A(A BBBAH�6T��gF�B�E �B(�A0�A8�DP?8E0A(B BBB447x���F�F�A �D(�D0�(G ABBl7��HI<�7���F�A�A �G0�8K@�8A0d
 AABD4�7ȭ��E�H�G z
AAH`
AAFX�7 ��tF�A�A �n(0B8B@BHBPI(H0L8I@Q q(M0Y(A H
ABI�X8D���F�E�D �D(�F@c
(D ABBK_
(D ABBH^
(D ABBI�
(A ABBJ�HJPTXM`I@��8`��F�E�E �A(�D0�E
(A BBBGN
(D EBBM\8J@SHMPI0A
(A BBBIs
(A BBBF@(D BBBt9H���9D���9@��<�9l���9h���9d���9`��:l��:h��(:d��<:`��P:\��d:h��x:t���:����:���&HK R�:���.TY �:���A��
EO
A,�:����E�X z
AHT(J0L(A ,(;$���Z�A�A �XCBG���0X;����F�D�D �G0]
 AABJ@�;@���F�D�G �G@gHWPAXB`N@n
 AABGL�;����F�B�B �B(�A0��
(B BBBFA
(B BBBG$ <���3F�D�G TGB0H<���F�D�D �G0j
 AABED|<����B�B�B �B(�D0�A8�M@�8D0A(B BBB@�<x��B�H�B �B(�A0�D@�
0D(B BBBG=T��0E�S
HK((=d���E�D�D ^
AAC(T=Ƚ�yE�D�D S
AAF$�=��^B�XJ sDB$�=T��CA�XJ ]AA�=|��ADPO ]$�=���XB�PN qDB$>��XB�PN qDB4@>���F�A�A �\
ABItABx>���QE�q
J$�>ؿ�^B�XJ sDB(�>��sE�D�D }
CDOH�>d���F�A�A �X(K0K8A@I(H0I8H@Q A
AHD,4?����B�K0Y8O@P8A0T
EA4d?h���A�A�D �
AADY
CAAH�? ���F�E�A �D(�G0y
(C ABBHb
(D DBBJH�?����F�E�A �D(�G0{
(A ABBHk
(A ABBG(4@����E�hf B(B0IG
I$`@���^B�XJ sDB`�@���pB�E�E �A(�D0�{
(D BBBJ�
(D BBBDY
(D BBBE`�@���SB�B�B �B(�A0�A8�DPw
8A0A(B BBBA�
8A0A(B BBBG,PA����B�A�A �q
FBKH�AL��F�B�B �B(�A0�A8�DPM
8D0A(B BBBL@�A���E�pP D(B0B8B@BHBPIH L(I0QG
G$B���CA�XJ ]AA`8B����B�B�B �B(�A0�A8�G� I�!8�!H�!N�!A�!Z
8D0A(B BBBH�B`��&HWL�Bx��:B�B�B �A(�C0��
(A BBBIN(A BBBCh��9HV
BX$C���(8C���aE�aP D(B0IG
D0dC����F�E�D �G0b
 ABBCx�CD��3F�G�E �E(�D0�C8�D`8hNpRxA�H�H�P`^
8D0A(B BBBJD
8A0A(B BBBB(D���E�D�D }
AAD$@D���CA�XJ ]AA(hD���oE�D�G0W
AAA$�D���CA�XJ ]AA(�D ���E�D�D n
AAC$�D���CA�XJ ]AA8E����F�B�D �D(�D`�
(A ABBAHLE@��uF�B�E �E(�A0�D8�DpJ
8A0A(B BBBAH�Et��uF�B�E �E(�A0�D8�DpJ
8A0A(B BBBAp�E���RF�B�E �E(�A0�V
(E BBBD{
(E HDBJZ
(E BBBKF(E BBB(XF���E�nI uAA
MH�F���F�A�A �](L0K8B@I H(L0I8B@Q o
IEL$�F\��GE�YI VAD�F���BE�q
JAG���	,G���	8@G����B�E�D �D(�D@�
(A ABBAD|G0���F�B�H �D(�A0�X8G@^8A0g
(D BBBL<�G���lF�H�A �D(�D0X8H@g(D ABBH���	H���0,H����F�D�D �G@g
 AABA`HL��tHH��	�HD��)HQ
GD8�HT���F�L�A �A(�D`?
(A ABBC<�H����F�B�E �E(�A0�b
(E BBBH$I��8I��sl�V
Ff�HXIt���F�E�D �D(�L0~
(A ABBEg(C ABB�I����I����I����I����I���J���Ya�[
DX8(J$���F�B�A �D(�D0h
(A ABBI(dJ���BF�D�D �lAB�J���QE�FH�J ���F�B�A �A(�D0B
(D ABBG|(Q ABB�J���K���" K���+4K���Ya�[
DXTTK���F�E�D �D(�F0x8J@THMPI(A ABBE0H(D ABB@�Kp���F�B�E �D(�D0�DPv
0A(A BBBI@�K���F�B�E �D(�D0�DPv
0A(A BBBI$4L���^B�XJ sDB\L��Ya�[
DX(|L@��<F�D�D �fAB$�LT��VE�D�D EAA�L���HK�L���Ya�[
DX$M���E�D�D nAA80M,��F�E�D �[
IKJ�
BBF8lM��F�E�D �[
IKJ�
BBF�M���!E�[�M���LE��M��HK�M$��QE�n
EXNd��QE�n
EX�8N����F�E�D �D(�F0w
(D ABBGP
(D ABBGL
(G ABBH�
(D ABBH|8J@SHMPI(A ABB8�N��F�B�A ��
BBD�
BBHTO����F�A�G c
DBIK
ABBN
DBLK
JBIdAE\O���Ya�[
DX8|O����F�E�E �D(�D0��(A BBB$�OT��vE�D�D eAA�O���HK�O���P��� P���4P���Ya�[
DXTP��9\\(lP0��uZ�T
Ba�W�H��P���	(�P���
F�A�A ��AB0�Pd��PF�H�G Z
JGHDABQ����b�[�S�$,Q���PF�A�G0~AB$TQ��QF�A�G0ABH|QP��kF�L�A �D(�D0e
(H JBBJD(A ABB@�Qt���I�A�A �J
ABHF
HKGWABR���%E�_(R���'E�a4DR���EF�E�D �D(�L0[(A ABB@|R���F�B�B �A(�D0�D@@
0A(A BBBE�Rl��HNL�Rt��mF�B�B �B(�A0�A8�D�_
8A0A(B BBBEH(S���KF�G�B �B(�A0�A8�DP8J0A(B BBB@tS����F�G�J �D(�D0�GPQ
0A(A BBBA�S4��Ya�[
DX`�St��rF�E�E �E(�D0�A8�Dp
8A0A(B BBBDc8A0A(B BBB8<T����F�B�D �A(�G@�
(A ABBDxTD��*E�]�TX��RE�L�T���;E�p4�T���yE�C�G s
CAJK
JAJU��Ya�[
DXP$UH��~F�E�B �A(�D0�D@�HFPRXH`U@C
0A(A BBBKHxUt���F�E�D �D(�D0�
(A ABBJD(A ABB��U���F�B�B �E(�D0�D8�DP�
8C0A(B BBBOD
8A0A(B BBBB�
8D0A(B BBBK[
8H0A(B BBBLK
8J0A(B BBBJ�tV��F�B�E �E(�D0�D8�F@�H[PNHA@D
8A0A(B BBBG{
8G0A(B BBBEL
8G0O(B BBBFK
8J0A(B BBBJ\WT��DF�E�D �A(�G0
(D ABBI~
(G DEPJD
(Q ABBFxxWD��,F�E�E �E(�D0�A8�G@�
8D0A(B BBBHD
8A0A(B BBBBD
8Q0A(B BBBJh�W���<F�B�E �D(�D0�G@s
0A(A BBBIl
0A(A BBBDY0C(A BBBx`X���F�B�E �E(�D0�D8�GPt
8A0A(B BBBA�
8A0A(B BBBDq8C0A(B BBBh�X��<F�B�E �D(�D0�G@s
0A(A BBBIl
0A(A BBBDY0C(A BBBhHY���<F�B�E �D(�D0�G@s
0A(A BBBIl
0A(A BBBDY0C(A BBBl�Y����F�B�E �D(�D0�G@s
0A(A BBBI�
0F(A BBBEY
0C(A BBBE@$Z���F�B�A �]
DBDL
EEJVDBhZD�HK�ZL�Ya�[
DXH�Z���F�E�E �E(�D0�A8�DP�
8D0A(B BBBA$�Z��EE�D�G rAA[�E�TH0[��F�B�B �E(�D0�C8�DPr
8D0A(B BBBG$|[��mE�D�D \AA�[��HKL�[��UF�A�D ��
DEIM(M0V(A O
HDLAAB@\��F�A�D ��
DEHX
HDLAAB�P\���F�B�E �D(�D0�O8J@SHMPI0A
(A BBBA]
(D BBBIi
(D EBBJI
(D EBBJ�
(D BBBI(�\���F�D�D �
ABE]X�Ya�[
DX4]��Ya�[
DXT]��Ya�[
DX$t]	�ZE�D�D IAAh�]P	��F�E�D �D(�F08J@THMPI(A ABBF0L
(D ABBKN
(A ABBL|^�	��F�B�B �B(�D0�G8�G�l
8A0A(B BBBD�
8Q0A(B BBBHZ
8J0H(B BBBL�^$
�Ya�[
DXh�^d
��F�B�E �D(�D0�s8J@THMPI0A
(A BBBDZ
(A BBBGL(A BBBT_�
��F�E�D �D(�F0q8J@THMPI(A ABBD0H(D AFB$l_ �rE�D�D aAA�_x�HK0�_���E�A�G s
DAKYCA�_��8E�k�_ �8E�k`D�KE�c
HZ8`t�KE�c
HZX`��VE�n
E]x`��QE�n
EX<�`$��F�D�G {
ABAf
HEMDQB8�`���F�A�D �@
HDI#
ABJ(a�F�D�D �
IEG@a��jF�v
TY`aL�jF�v
TY4�a���F�L�J �D(A0J(J EAB0�a���F�A�D �G0�
 AABA�a��LE�~b��LE�~`$b�DF�H�B �E(�D0�A8�D@C
8A0A(B BBBE�8D0A(B BBB(�b��eF�D�D i
ABF�b8�E�W��b<�tF�E�E �E(�D0�D8�G�
8A0A(B BBBDN�Z�N�A�n
8J0A(B BBBHY�V�O�A�HXc4��E�D�D Q(H0U(A M(K0S(A K
AAKDAA8�c��B�D�D �H(F0v(A j
AEIX�c���F�B�E �D(�D0�s8J@THMPI0A
(A BBBDL(D EBB$<d��IF�A�G nFBdd�Txd��F�B�A �D(�Dp8xF�F�F�F�B�Ips
(A ABBK�d��AO�U�0�d���E�A�G Q
AAHrAAH el �F�B�B �A(�D0��
(E FEJLA(A BBB4le@!��F�A�D �a
ABA[DB�e�!��e�!��e�!�IN�o��e�!��e�!�0f�!�sE�D�G |
AAJDQADf"�@Xf"�cF�H�B �E(�D0�D@�
0A(B BBBGD�f@#��F�B�A �{
BBHA
BBKA
IKK�f�#�UR�^
H\g$��E��
G0$g�$�OE�G�D g
DALDAA8Xg�$�dF�E�D �D(�D@�
(A ABBH0�g�%�	F�D�A �DPI
 AABDt�g�&�tB�H�G �B(�A0�A8�Jp�xU�O�B�B�A�Spe
8A0A(B BBBJIxH�O�P�Ip@h�(�yE�]
F`h<)�#E�X|hP)�#E�X,�hd)��F�A�D ��
KGL@�h$*��F�B�B �D(�D0�D��
0A(A BBBA$i�*�CA�XJ ]AA4i+�ADPO ]Ti8+�ADPO ]tih+�ADPO ]�i�+�ADPO ](�i�+�E�D�D �
AAM$�i�,�^B�XJ sDBj�,�(E�b$j�,�9E�sL@j-�nB�I�B �B(�A0�A8�I�
8A0A(B BBBE�j<.��j8.�	(�j4.��E�jf B(B0IG
G<�j�.��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 ABBH0P$l81�E�A�G �(G0I8H@DHBPBXB`I H(I0I8B@WAAG $xl2�^B�XJ sDBD�l<2��B�B�B �B(�A0�A8�G@y8D0A(B BBB8�l�2�lF�E�E �A(�I0�z(H GBBH$m�2��F�J�E �A(�D0�^8N@V8A0A
(A BBBKpm|3�eE�[�m�3��E��
G �m`4�gE�D q
AE�m�4�7E�m\�m�4�F�D�B �B(�A0�A8�G�
8A0A(B BBBKX�E�J�A�0Ln�8�fE�I�G U
GANIIOd�n�8�oB�B�G �E(�D0�D8�MpJ
8A0A(B BBBG�xN�`xApaxG�gxAp\�n�:�IO�B�B �B(�D0�F
(B BBBI��(E� B�B�B�U0�����`Ho�;��R�E�E �D(�G0^
(J� E�E�B�Eg
(K� E�B�B�ED(A BBB4�o@<�nF�H�K �
BBLABB0�ox<��F�D�D �D@�
 AABAp=��E�q
J`8p�=�yB�B�B �B(�A0�A8�DP�
8D0A(B BBBH\
8A0A(B BBBBH�p�>��B�B�B �B(�A0�A8�D@�
8D0A(B BBBM$�p4?�CA�XJ ]AAxq\?��E�A�G }(O0K8B@I G(O0I8B@Q(H0O8I@AHBPBXB`Q(H0N8I@BHBPQ(H0O8I@WAAJ �q�@�PE�JL�q�@��B�B�A �A(�D0�
(A DBBJD
(A ABBFX�q4B�B�B�A �A(�G@oH]PFHA@T
(A ABBF[
(O ABBI\Tr�B��B�B�A �A(�G�8�]�F�A�T
(A ABBE[
(O ABBIL�rxD��F�H�D �a
ABJI(J0g(A C
ABOlDJs�D�wE�l
o4 s\E��F�B�K �k
BBFwBBXs�E�(ls�E�3F�G�D �ODJ(�sF��E�D�J@[
AAH@�s�F��F�B�A �t
DBEL
EEJVDBtG�t G�.HI I(E0NH<t0G�QF�B�B �B(�A0�A8�DP�
8D0A(B BBBF�tDH�	(�t@H�FF�I�N �RDN$�tdH�3F�D�G TGB(�t|H�FF�I�N �RDN$u�H�3F�D�G TGB(Du�H�@F�I�K �RDK$pu�H�3F�D�G TGB�u�H� �u�H�eE�G0R
AA�u<I�Ya�[
DXX�u|I��F�B�E �D(�D0�s8J@THMPI0A
(A BBBD}(D BBBTLvJ��F�E�D �D(�F0q8J@THMPI(A ABBD0H(D ABBH�vHJ��F�B�B �E(�D0�D8�D`�
8A0A(B BBBAH�v�J��F�B�B �E(�D0�D8�D`�
8A0A(B BBBA<wpK�IE�~Xw�K�Ya�[
DXLxw�K��F�B�B �E(�D0�D8�D��
8A0A(B BBBHT�w$N��F�E�D �D(�F0x8J@THMPI(A ABBE0D(I ABB( x|N�cF�D�D �CNB(Lx�N�LF�D�D �zAB(xx�N�LF�D�D �zABL�xO�^F�B�B �B(�A0�A8�G�9
8A0A(B BBBA�xP�DE�yHyLP�"F�B�B �E(�A0�D8�D��
8D0A(B BBBHd\y0R�@F�B�E �B(�A0�D8�D��
8A0A(B BBBB
8J0A(B BBBE�yT�QL�_
E0�yLT��F�A�A �G`�
 DABAz�T�Ya�[
DXT4z(U��F�E�D �D(�F0q8J@THMPI(A ABBD0H(D ABB�zpU�<E�q�z�U�%F�^�z�U�9\\�z�U�Ya�[
DX�zV�{V�
${V�
8{V�
L{V�
`{V�
t{V�
�{V�
�{V��{V��{V��{$V��{0V�|<V�KH0}
A|pV�0||V�D|�V�X|�V�'HN Pt|�V��|�V��|�V�KH }
A�|�V��|W�IH {
A�|4W�IH {
A}hW�E�Y }lW�Ya�[
DXH@}�W�F�B�B �B(�A0�D8�D��
8A0A(B BBBAp�}�X��F�E�E �E(�D0�D8�Dp�
8C0A(B BBBK�xG�ZxApX
8D0A(B BBBE4~�[�vF�E�D �K
EBOABB\8~�[�!F�E�E �D(�G0�
(D BBBJD
(A BBBEK
(J BBBMH�~�\��F�E�D �D(�G0~
(K HDBOK
(J ABBFL�~(]��F�E�D �D(�G0B
(F KBBOK
(J ABBF|4�]��F�B�B �E(�D0�D8�G�P
8A0A(B BBBHE
8J0A(B BBBH�
8D0A(B BBBJ@�`��F�E�D �n
DBEL
EEJVDB��`�Ya�[
DX8��`��F�B�D �D(�G@�
(A ABBI(T��b�F�A�D �
NBK���c��E�H
CH���c�B�B�A �D(�G0�
(D ABBHl(C ABB(��d��F�A�K ��EB�de�\\0��e�QE�n
EX4P��e��F�D�A �^
DENVCB0��Tf�vE�D�G {
ADHYCA���f�E�L؁�f�Ya�[
DX<���f�F�B�B �D(�A0��
(A OBBO$8��g�PF�D�G yCB<`��g��F�B�A �A(�G�s
(A ABBF`���i�wF�B�B �B(�A0�D8�DPW
8D0A(B BBBGXM`WXAP[XH`ZXAPX��m��F�B�B �A(�A0�G`�hFpFxF�F�B�I`l
0D(A BBBA`�o�^E�n
M]X��Lo�WF�B�B �B(�D0�D8�DpQ
8C0A(B BBBKbxH�mxAp8܃Pp�F�B�B �A(�J�V
(A BBBFH�4q��F�B�B �B(�D0�D8�I`�
8A0A(B BBBKXd��r��F�B�B �D(�D0�GPz
0A(A BBBEd
0O(H BBBE��,t�Ya�[
DX$�lt�ZE�D�D IAAT��t��F�E�D �D(�F0q8N@UHMPI(A ABBG0G(D ABB`��t�HK\x�u��F�B�B �B(�A0�A8�G�V
8D0A(B BBBH��]�F�A�(؅�x�iF�B�A �RBE<��x��E�D�F p
AAG\
HDHDOHXD�Hy��F�B�E �D(�D0�s8N@UHMPI0A
(A BBBGL(D DBB\���y��F�E�E �D(�D0�D
(X BBBFq
(A BBBHA(O IBB�,z�VE�m
F]H �lz��F�E�A �A(�G0H
(D ABBK\(D ABBHl��z��F�E�A �A(�G0E
(D ABBF\(D ABB`��{�cF�B�E �L(�A0�A8�D`�
8D0A(B BBBKO
8A0A(B BBBG�|�/Hb$4�(|�PF�D�G gHK4\�P|��F�E�B �A(�G0�(J EII8���|�E��H L(K0IOH L(K0IG
AЈ�}�)HY4��}��B�B�D ��
EBJaBB ��~�(4��~�zB�A�G M
DBC@`��~��B�B�D �{(G0f(D A
BBGABB$����3F�D�G TGB4̉���F�E�E �D(�D0W(M BBB8����F�B�A �A(�F0o
(D ABBHd@�\��YB�B�B �E(�D0�D8�DPm
8A0A(B BBBA�
8C0A(B BBBF$��T��IF�D�G mDB$Њ|��IF�D�G mDB$�����3F�D�G TGB( �����F�D�A ��JELL�����F�B�B �B(�A0�D8�J�0
8A0A(B BBBKX��@��OF�E�E �B(�D0�D8�J���V�R�A�v
8A0A(B BBBE�P���@�����e�s�n�����b�r�j�����J���U���������������t����������������������t�������������������t�`e) f)�e)�e)�f)�f)`f) g)����������������`g)Pg)���������g)���������g)���h)@h)h)=���"�*�6�:�B�8�p7P:�; e)�d)�d)������i)I����g)�������h)�j)g�{���`�����������������,����&�0�5�D�Z	Z�u�����������������,�5�M�U�l�q�����������X���x��� ����@�����	�!�����'�=��0�&3'�&�v�v�v�v�vww$w2wIwUwow�w�w�w�w�w�wxxx-x?xNxgxqx�xp
���d)�d)���o��x
yt)xQ  	���o���op���o�o2����o��p)0p@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�����������К���� �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��)�h7q$/usr/lib/debug/.dwz/x86_64-linux-gnu/gnome-shell.debugI��3��m�Յ��ð�M]��eef9782e3417aa1ad999c879b7c9ec0421437c.debug���7.shstrtab.note.gnu.property.note.gnu.build-id.gnu.hash.dynsym.dynstr.gnu.version.gnu.version_r.rela.dyn.rela.plt.init.plt.got.plt.sec.text.fini.rodata.gresource.shell_js_resources.eh_frame_hdr.eh_frame.init_array.fini_array.data.rel.ro.dynamic.data.bss.gnu_debugaltlink.gnu_debuglink�� ��$1���o���;xx�nC��yK���o2�2�8	X���opp�g  qB  xQ{ppv p p`6�����p���P6�@�@�o������
���eW �h7h7 q$���(��(����(��(����d)�T)��d)�T)��d)�T)� �p)�`)��t)d)��)�)� ��)��)� ��)K(Ԁ)4�)7

Anon7 - 2022
AnonSec Team