From 9b7e7621a42966c79b20fa8fb5ba5742af3b1dcf Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 15:02:48 -0600 Subject: [PATCH 01/38] Add vscode and clang-format based on linux kernel --- .clang-format | 562 ++++++++++++++++++++++ .vscode/c_cpp_properties.json | 29 ++ widget.cpp | 845 ++++++++++++++++++---------------- widget.h | 56 +-- 4 files changed, 1060 insertions(+), 432 deletions(-) create mode 100644 .clang-format create mode 100644 .vscode/c_cpp_properties.json diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..a7202fe --- /dev/null +++ b/.clang-format @@ -0,0 +1,562 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# clang-format configuration file. Intended for clang-format >= 4. +# +# For more information, see: +# +# Documentation/process/clang-format.rst +# https://clang.llvm.org/docs/ClangFormat.html +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +# +--- +AccessModifierOffset: -8 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +#AlignEscapedNewlines: Left # Unknown to clang-format-4.0 +AlignOperands: true +AlignTrailingComments: false +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + #AfterExternBlock: false # Unknown to clang-format-5.0 + BeforeCatch: false + BeforeElse: false + IndentBraces: false + #SplitEmptyFunction: true # Unknown to clang-format-4.0 + #SplitEmptyRecord: true # Unknown to clang-format-4.0 + #SplitEmptyNamespace: true # Unknown to clang-format-4.0 +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +#BreakBeforeInheritanceComma: false # Unknown to clang-format-4.0 +BreakBeforeTernaryOperators: false +BreakConstructorInitializersBeforeComma: false +#BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +#CompactNamespaces: false # Unknown to clang-format-4.0 +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 8 +ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +#FixNamespaceComments: false # Unknown to clang-format-4.0 + +# Taken from: +# git grep -h '^#define [^[:space:]]*for_each[^[:space:]]*(' include/ \ +# | sed "s,^#define \([^[:space:]]*for_each[^[:space:]]*\)(.*$, - '\1'," \ +# | sort | uniq +ForEachMacros: + - 'apei_estatus_for_each_section' + - 'ata_for_each_dev' + - 'ata_for_each_link' + - '__ata_qc_for_each' + - 'ata_qc_for_each' + - 'ata_qc_for_each_raw' + - 'ata_qc_for_each_with_internal' + - 'ax25_for_each' + - 'ax25_uid_for_each' + - '__bio_for_each_bvec' + - 'bio_for_each_bvec' + - 'bio_for_each_bvec_all' + - 'bio_for_each_integrity_vec' + - '__bio_for_each_segment' + - 'bio_for_each_segment' + - 'bio_for_each_segment_all' + - 'bio_list_for_each' + - 'bip_for_each_vec' + - 'bitmap_for_each_clear_region' + - 'bitmap_for_each_set_region' + - 'blkg_for_each_descendant_post' + - 'blkg_for_each_descendant_pre' + - 'blk_queue_for_each_rl' + - 'bond_for_each_slave' + - 'bond_for_each_slave_rcu' + - 'bpf_for_each_spilled_reg' + - 'btree_for_each_safe128' + - 'btree_for_each_safe32' + - 'btree_for_each_safe64' + - 'btree_for_each_safel' + - 'card_for_each_dev' + - 'cgroup_taskset_for_each' + - 'cgroup_taskset_for_each_leader' + - 'cpufreq_for_each_entry' + - 'cpufreq_for_each_entry_idx' + - 'cpufreq_for_each_valid_entry' + - 'cpufreq_for_each_valid_entry_idx' + - 'css_for_each_child' + - 'css_for_each_descendant_post' + - 'css_for_each_descendant_pre' + - 'device_for_each_child_node' + - 'displayid_iter_for_each' + - 'dma_fence_chain_for_each' + - 'do_for_each_ftrace_op' + - 'drm_atomic_crtc_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane' + - 'drm_atomic_crtc_state_for_each_plane_state' + - 'drm_atomic_for_each_plane_damage' + - 'drm_client_for_each_connector_iter' + - 'drm_client_for_each_modeset' + - 'drm_connector_for_each_possible_encoder' + - 'drm_for_each_bridge_in_chain' + - 'drm_for_each_connector_iter' + - 'drm_for_each_crtc' + - 'drm_for_each_crtc_reverse' + - 'drm_for_each_encoder' + - 'drm_for_each_encoder_mask' + - 'drm_for_each_fb' + - 'drm_for_each_legacy_plane' + - 'drm_for_each_plane' + - 'drm_for_each_plane_mask' + - 'drm_for_each_privobj' + - 'drm_mm_for_each_hole' + - 'drm_mm_for_each_node' + - 'drm_mm_for_each_node_in_range' + - 'drm_mm_for_each_node_safe' + - 'flow_action_for_each' + - 'for_each_acpi_dev_match' + - 'for_each_active_dev_scope' + - 'for_each_active_drhd_unit' + - 'for_each_active_iommu' + - 'for_each_aggr_pgid' + - 'for_each_available_child_of_node' + - 'for_each_bio' + - 'for_each_board_func_rsrc' + - 'for_each_bvec' + - 'for_each_card_auxs' + - 'for_each_card_auxs_safe' + - 'for_each_card_components' + - 'for_each_card_dapms' + - 'for_each_card_pre_auxs' + - 'for_each_card_prelinks' + - 'for_each_card_rtds' + - 'for_each_card_rtds_safe' + - 'for_each_card_widgets' + - 'for_each_card_widgets_safe' + - 'for_each_cgroup_storage_type' + - 'for_each_child_of_node' + - 'for_each_clear_bit' + - 'for_each_clear_bit_from' + - 'for_each_cmsghdr' + - 'for_each_compatible_node' + - 'for_each_component_dais' + - 'for_each_component_dais_safe' + - 'for_each_comp_order' + - 'for_each_console' + - 'for_each_cpu' + - 'for_each_cpu_and' + - 'for_each_cpu_not' + - 'for_each_cpu_wrap' + - 'for_each_dapm_widgets' + - 'for_each_dev_addr' + - 'for_each_dev_scope' + - 'for_each_dma_cap_mask' + - 'for_each_dpcm_be' + - 'for_each_dpcm_be_rollback' + - 'for_each_dpcm_be_safe' + - 'for_each_dpcm_fe' + - 'for_each_drhd_unit' + - 'for_each_dss_dev' + - 'for_each_dtpm_table' + - 'for_each_efi_memory_desc' + - 'for_each_efi_memory_desc_in_map' + - 'for_each_element' + - 'for_each_element_extid' + - 'for_each_element_id' + - 'for_each_endpoint_of_node' + - 'for_each_evictable_lru' + - 'for_each_fib6_node_rt_rcu' + - 'for_each_fib6_walker_rt' + - 'for_each_free_mem_pfn_range_in_zone' + - 'for_each_free_mem_pfn_range_in_zone_from' + - 'for_each_free_mem_range' + - 'for_each_free_mem_range_reverse' + - 'for_each_func_rsrc' + - 'for_each_hstate' + - 'for_each_if' + - 'for_each_iommu' + - 'for_each_ip_tunnel_rcu' + - 'for_each_irq_nr' + - 'for_each_link_codecs' + - 'for_each_link_cpus' + - 'for_each_link_platforms' + - 'for_each_lru' + - 'for_each_matching_node' + - 'for_each_matching_node_and_match' + - 'for_each_member' + - 'for_each_memcg_cache_index' + - 'for_each_mem_pfn_range' + - '__for_each_mem_range' + - 'for_each_mem_range' + - '__for_each_mem_range_rev' + - 'for_each_mem_range_rev' + - 'for_each_mem_region' + - 'for_each_migratetype_order' + - 'for_each_msi_entry' + - 'for_each_msi_entry_safe' + - 'for_each_msi_vector' + - 'for_each_net' + - 'for_each_net_continue_reverse' + - 'for_each_netdev' + - 'for_each_netdev_continue' + - 'for_each_netdev_continue_rcu' + - 'for_each_netdev_continue_reverse' + - 'for_each_netdev_feature' + - 'for_each_netdev_in_bond_rcu' + - 'for_each_netdev_rcu' + - 'for_each_netdev_reverse' + - 'for_each_netdev_safe' + - 'for_each_net_rcu' + - 'for_each_new_connector_in_state' + - 'for_each_new_crtc_in_state' + - 'for_each_new_mst_mgr_in_state' + - 'for_each_new_plane_in_state' + - 'for_each_new_private_obj_in_state' + - 'for_each_node' + - 'for_each_node_by_name' + - 'for_each_node_by_type' + - 'for_each_node_mask' + - 'for_each_node_state' + - 'for_each_node_with_cpus' + - 'for_each_node_with_property' + - 'for_each_nonreserved_multicast_dest_pgid' + - 'for_each_of_allnodes' + - 'for_each_of_allnodes_from' + - 'for_each_of_cpu_node' + - 'for_each_of_pci_range' + - 'for_each_old_connector_in_state' + - 'for_each_old_crtc_in_state' + - 'for_each_old_mst_mgr_in_state' + - 'for_each_oldnew_connector_in_state' + - 'for_each_oldnew_crtc_in_state' + - 'for_each_oldnew_mst_mgr_in_state' + - 'for_each_oldnew_plane_in_state' + - 'for_each_oldnew_plane_in_state_reverse' + - 'for_each_oldnew_private_obj_in_state' + - 'for_each_old_plane_in_state' + - 'for_each_old_private_obj_in_state' + - 'for_each_online_cpu' + - 'for_each_online_node' + - 'for_each_online_pgdat' + - 'for_each_pci_bridge' + - 'for_each_pci_dev' + - 'for_each_pci_msi_entry' + - 'for_each_pcm_streams' + - 'for_each_physmem_range' + - 'for_each_populated_zone' + - 'for_each_possible_cpu' + - 'for_each_present_cpu' + - 'for_each_prime_number' + - 'for_each_prime_number_from' + - 'for_each_process' + - 'for_each_process_thread' + - 'for_each_prop_codec_conf' + - 'for_each_prop_dai_codec' + - 'for_each_prop_dai_cpu' + - 'for_each_prop_dlc_codecs' + - 'for_each_prop_dlc_cpus' + - 'for_each_prop_dlc_platforms' + - 'for_each_property_of_node' + - 'for_each_registered_fb' + - 'for_each_requested_gpio' + - 'for_each_requested_gpio_in_range' + - 'for_each_reserved_mem_range' + - 'for_each_reserved_mem_region' + - 'for_each_rtd_codec_dais' + - 'for_each_rtd_components' + - 'for_each_rtd_cpu_dais' + - 'for_each_rtd_dais' + - 'for_each_set_bit' + - 'for_each_set_bit_from' + - 'for_each_set_clump8' + - 'for_each_sg' + - 'for_each_sg_dma_page' + - 'for_each_sg_page' + - 'for_each_sgtable_dma_page' + - 'for_each_sgtable_dma_sg' + - 'for_each_sgtable_page' + - 'for_each_sgtable_sg' + - 'for_each_sibling_event' + - 'for_each_subelement' + - 'for_each_subelement_extid' + - 'for_each_subelement_id' + - '__for_each_thread' + - 'for_each_thread' + - 'for_each_unicast_dest_pgid' + - 'for_each_vsi' + - 'for_each_wakeup_source' + - 'for_each_zone' + - 'for_each_zone_zonelist' + - 'for_each_zone_zonelist_nodemask' + - 'fwnode_for_each_available_child_node' + - 'fwnode_for_each_child_node' + - 'fwnode_graph_for_each_endpoint' + - 'gadget_for_each_ep' + - 'genradix_for_each' + - 'genradix_for_each_from' + - 'hash_for_each' + - 'hash_for_each_possible' + - 'hash_for_each_possible_rcu' + - 'hash_for_each_possible_rcu_notrace' + - 'hash_for_each_possible_safe' + - 'hash_for_each_rcu' + - 'hash_for_each_safe' + - 'hctx_for_each_ctx' + - 'hlist_bl_for_each_entry' + - 'hlist_bl_for_each_entry_rcu' + - 'hlist_bl_for_each_entry_safe' + - 'hlist_for_each' + - 'hlist_for_each_entry' + - 'hlist_for_each_entry_continue' + - 'hlist_for_each_entry_continue_rcu' + - 'hlist_for_each_entry_continue_rcu_bh' + - 'hlist_for_each_entry_from' + - 'hlist_for_each_entry_from_rcu' + - 'hlist_for_each_entry_rcu' + - 'hlist_for_each_entry_rcu_bh' + - 'hlist_for_each_entry_rcu_notrace' + - 'hlist_for_each_entry_safe' + - 'hlist_for_each_entry_srcu' + - '__hlist_for_each_rcu' + - 'hlist_for_each_safe' + - 'hlist_nulls_for_each_entry' + - 'hlist_nulls_for_each_entry_from' + - 'hlist_nulls_for_each_entry_rcu' + - 'hlist_nulls_for_each_entry_safe' + - 'i3c_bus_for_each_i2cdev' + - 'i3c_bus_for_each_i3cdev' + - 'ide_host_for_each_port' + - 'ide_port_for_each_dev' + - 'ide_port_for_each_present_dev' + - 'idr_for_each_entry' + - 'idr_for_each_entry_continue' + - 'idr_for_each_entry_continue_ul' + - 'idr_for_each_entry_ul' + - 'in_dev_for_each_ifa_rcu' + - 'in_dev_for_each_ifa_rtnl' + - 'inet_bind_bucket_for_each' + - 'inet_lhash2_for_each_icsk_rcu' + - 'key_for_each' + - 'key_for_each_safe' + - 'klp_for_each_func' + - 'klp_for_each_func_safe' + - 'klp_for_each_func_static' + - 'klp_for_each_object' + - 'klp_for_each_object_safe' + - 'klp_for_each_object_static' + - 'kunit_suite_for_each_test_case' + - 'kvm_for_each_memslot' + - 'kvm_for_each_vcpu' + - 'list_for_each' + - 'list_for_each_codec' + - 'list_for_each_codec_safe' + - 'list_for_each_continue' + - 'list_for_each_entry' + - 'list_for_each_entry_continue' + - 'list_for_each_entry_continue_rcu' + - 'list_for_each_entry_continue_reverse' + - 'list_for_each_entry_from' + - 'list_for_each_entry_from_rcu' + - 'list_for_each_entry_from_reverse' + - 'list_for_each_entry_lockless' + - 'list_for_each_entry_rcu' + - 'list_for_each_entry_reverse' + - 'list_for_each_entry_safe' + - 'list_for_each_entry_safe_continue' + - 'list_for_each_entry_safe_from' + - 'list_for_each_entry_safe_reverse' + - 'list_for_each_entry_srcu' + - 'list_for_each_prev' + - 'list_for_each_prev_safe' + - 'list_for_each_safe' + - 'llist_for_each' + - 'llist_for_each_entry' + - 'llist_for_each_entry_safe' + - 'llist_for_each_safe' + - 'mci_for_each_dimm' + - 'media_device_for_each_entity' + - 'media_device_for_each_intf' + - 'media_device_for_each_link' + - 'media_device_for_each_pad' + - 'nanddev_io_for_each_page' + - 'netdev_for_each_lower_dev' + - 'netdev_for_each_lower_private' + - 'netdev_for_each_lower_private_rcu' + - 'netdev_for_each_mc_addr' + - 'netdev_for_each_uc_addr' + - 'netdev_for_each_upper_dev_rcu' + - 'netdev_hw_addr_list_for_each' + - 'nft_rule_for_each_expr' + - 'nla_for_each_attr' + - 'nla_for_each_nested' + - 'nlmsg_for_each_attr' + - 'nlmsg_for_each_msg' + - 'nr_neigh_for_each' + - 'nr_neigh_for_each_safe' + - 'nr_node_for_each' + - 'nr_node_for_each_safe' + - 'of_for_each_phandle' + - 'of_property_for_each_string' + - 'of_property_for_each_u32' + - 'pci_bus_for_each_resource' + - 'pcl_for_each_chunk' + - 'pcl_for_each_segment' + - 'pcm_for_each_format' + - 'ping_portaddr_for_each_entry' + - 'plist_for_each' + - 'plist_for_each_continue' + - 'plist_for_each_entry' + - 'plist_for_each_entry_continue' + - 'plist_for_each_entry_safe' + - 'plist_for_each_safe' + - 'pnp_for_each_card' + - 'pnp_for_each_dev' + - 'protocol_for_each_card' + - 'protocol_for_each_dev' + - 'queue_for_each_hw_ctx' + - 'radix_tree_for_each_slot' + - 'radix_tree_for_each_tagged' + - 'rb_for_each' + - 'rbtree_postorder_for_each_entry_safe' + - 'rdma_for_each_block' + - 'rdma_for_each_port' + - 'rdma_umem_for_each_dma_block' + - 'resource_list_for_each_entry' + - 'resource_list_for_each_entry_safe' + - 'rhl_for_each_entry_rcu' + - 'rhl_for_each_rcu' + - 'rht_for_each' + - 'rht_for_each_entry' + - 'rht_for_each_entry_from' + - 'rht_for_each_entry_rcu' + - 'rht_for_each_entry_rcu_from' + - 'rht_for_each_entry_safe' + - 'rht_for_each_from' + - 'rht_for_each_rcu' + - 'rht_for_each_rcu_from' + - '__rq_for_each_bio' + - 'rq_for_each_bvec' + - 'rq_for_each_segment' + - 'scsi_for_each_prot_sg' + - 'scsi_for_each_sg' + - 'sctp_for_each_hentry' + - 'sctp_skb_for_each' + - 'shdma_for_each_chan' + - '__shost_for_each_device' + - 'shost_for_each_device' + - 'sk_for_each' + - 'sk_for_each_bound' + - 'sk_for_each_entry_offset_rcu' + - 'sk_for_each_from' + - 'sk_for_each_rcu' + - 'sk_for_each_safe' + - 'sk_nulls_for_each' + - 'sk_nulls_for_each_from' + - 'sk_nulls_for_each_rcu' + - 'snd_array_for_each' + - 'snd_pcm_group_for_each_entry' + - 'snd_soc_dapm_widget_for_each_path' + - 'snd_soc_dapm_widget_for_each_path_safe' + - 'snd_soc_dapm_widget_for_each_sink_path' + - 'snd_soc_dapm_widget_for_each_source_path' + - 'tb_property_for_each' + - 'tcf_exts_for_each_action' + - 'udp_portaddr_for_each_entry' + - 'udp_portaddr_for_each_entry_rcu' + - 'usb_hub_for_each_child' + - 'v4l2_device_for_each_subdev' + - 'v4l2_m2m_for_each_dst_buf' + - 'v4l2_m2m_for_each_dst_buf_safe' + - 'v4l2_m2m_for_each_src_buf' + - 'v4l2_m2m_for_each_src_buf_safe' + - 'virtio_device_for_each_vq' + - 'while_for_each_ftrace_op' + - 'xa_for_each' + - 'xa_for_each_marked' + - 'xa_for_each_range' + - 'xa_for_each_start' + - 'xas_for_each' + - 'xas_for_each_conflict' + - 'xas_for_each_marked' + - 'xbc_array_for_each_value' + - 'xbc_for_each_key_value' + - 'xbc_node_for_each_array_value' + - 'xbc_node_for_each_child' + - 'xbc_node_for_each_key_value' + - 'zorro_for_each_dev' + +#IncludeBlocks: Preserve # Unknown to clang-format-5.0 +IncludeCategories: + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +#IndentPPDirectives: None # Unknown to clang-format-5.0 +IndentWidth: 8 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: false +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +#ObjCBinPackProtocolList: Auto # Unknown to clang-format-5.0 +ObjCBlockIndentWidth: 8 +ObjCSpaceAfterProperty: true +ObjCSpaceBeforeProtocolList: true + +# Taken from git's rules +#PenaltyBreakAssignment: 10 # Unknown to clang-format-4.0 +PenaltyBreakBeforeFirstCallParameter: 30 +PenaltyBreakComment: 10 +PenaltyBreakFirstLessLess: 0 +PenaltyBreakString: 10 +PenaltyExcessCharacter: 100 +PenaltyReturnTypeOnItsOwnLine: 60 + +PointerAlignment: Right +ReflowComments: false +SortIncludes: false +#SortUsingDeclarations: false # Unknown to clang-format-4.0 +SpaceAfterCStyleCast: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +#SpaceBeforeCtorInitializerColon: true # Unknown to clang-format-5.0 +#SpaceBeforeInheritanceColon: true # Unknown to clang-format-5.0 +SpaceBeforeParens: ControlStatements +#SpaceBeforeRangeBasedForLoopColon: true # Unknown to clang-format-5.0 +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: false +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp03 +TabWidth: 8 +UseTab: Always +... + diff --git a/.vscode/c_cpp_properties.json b/.vscode/c_cpp_properties.json new file mode 100644 index 0000000..3956680 --- /dev/null +++ b/.vscode/c_cpp_properties.json @@ -0,0 +1,29 @@ +{ + "configurations": [ + { + "name": "Linux", + "includePath": [ + "${workspaceFolder}/**", + "/usr/include/qt6/QtCore", + "/usr/include/qt6", + "/usr/include/qt6/QtWidgets", + "/usr/include/qt6/QtGui" + ], + "defines": [], + "compilerPath": "/usr/bin/clang", + "cStandard": "c17", + "cppStandard": "c++20", + "intelliSenseMode": "linux-clang-x64", + "compilerArgs": [], + "mergeConfigurations": false, + "browse": { + "path": [ + "${workspaceFolder}/**", + "/usr/include/qt6/QtCore" + ], + "limitSymbolsToIncludedHeaders": true + } + } + ], + "version": 4 +} diff --git a/widget.cpp b/widget.cpp index ef3c3f6..ced5d76 100644 --- a/widget.cpp +++ b/widget.cpp @@ -1,435 +1,472 @@ #include "widget.h" -#include #include #include -#include -#include -#include -#include -#include -#include -#include #include +#include +#include #include +#include +#include +#include +#include +#include +#include -Widget::Widget(QWidget *parent) - : QWidget(parent) +Widget::Widget(QWidget *parent) : QWidget(parent) { - for (const QString &dirPath : QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { - qDebug() << "Loading applications from" << dirPath; - QDir applicationsDir(dirPath); - - for (const QFileInfo &file : applicationsDir.entryInfoList(QStringList("*.desktop"))) { - loadDesktopFile(file); - } - } - - // Check that we shit with multiple .desktop files, but some nodisplay files - for (const QString &appId : m_supportedMimetypes.keys()) { - if (!m_desktopFileNames.contains(appId)) { - qWarning() << appId << "does not have an associated desktop file!"; - continue; - } - - if (m_applicationNames[appId].isEmpty()) { - qWarning() << "Missing name" << appId; - m_applicationNames[appId] = appId; - } - } - - // Preload up front, so it doesn't get sluggish when selecting applications supporting a lot - const QIcon unknownIcon = QIcon::fromTheme("unknown"); - - // TODO: check if QT_QPA_PLATFORMTHEME is set to plasma or sandsmark, - // if so just use the functioning QIcon::fromTheme() - // We do this manually because non-Plasma-platforms icon loading is extremely slow (I blame GTK and its crappy icon cache) - for (const QString &searchPath : (QIcon::themeSearchPaths() + QIcon::fallbackSearchPaths())) { - loadIcons(searchPath + QIcon::themeName()); - loadIcons(searchPath); - } - - for (const QString &mimetypeName : m_supportedMimetypes.values()) { - if (m_mimeTypeIcons.contains(mimetypeName)) { - continue; - } - const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeName); - - QString iconName = mimetype.iconName(); - QIcon icon(m_iconPaths.value(iconName)); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - int split = iconName.lastIndexOf('+'); - if (split != -1) { - iconName.truncate(split); - icon = QIcon(m_iconPaths.value(iconName)); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - } - split = iconName.lastIndexOf('-'); - if (split != -1) { - iconName.truncate(split); - icon = QIcon(m_iconPaths.value(iconName)); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - } - icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - - m_mimeTypeIcons[mimetypeName] = unknownIcon; - } - - QHBoxLayout *mainLayout = new QHBoxLayout; - setLayout(mainLayout); - m_applicationList = new QTreeWidget; - mainLayout->addWidget(m_applicationList); - - QGridLayout *rightLayout = new QGridLayout; - mainLayout->addLayout(rightLayout); - - m_setDefaultButton = new QPushButton(tr("Set as default application for these file types")); - m_setDefaultButton->setEnabled(false); - - m_mimetypeList = new QListWidget; - m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); - - rightLayout->addWidget(m_mimetypeList); - rightLayout->addWidget(m_setDefaultButton); - - QStringList types = m_applications.keys(); - std::sort(types.begin(), types.end()); - for (const QString &type : types) { - QTreeWidgetItem *typeItem = new QTreeWidgetItem(QStringList(type)); - QStringList applications = m_applications[type].values(); - std::sort(applications.begin(), applications.end(), [=](const QString &a, const QString &b) { - return m_applicationNames[a] < m_applicationNames[b]; - }); - for (const QString &application : applications) { - QTreeWidgetItem *appItem = new QTreeWidgetItem(QStringList(m_applicationNames[application])); - appItem->setData(0, Qt::UserRole, application); - appItem->setIcon(0, QIcon::fromTheme(m_applicationIcons[application])); - typeItem->addChild(appItem); - } - m_applicationList->addTopLevelItem(typeItem); - } - m_applicationList->setHeaderHidden(true); - - connect(m_applicationList, &QTreeWidget::itemSelectionChanged, this, &Widget::onMimetypeSelected); - connect(m_setDefaultButton, &QPushButton::clicked, this, &Widget::onSetDefaultClicked); + for (const QString &dirPath : QStandardPaths::standardLocations( + QStandardPaths::ApplicationsLocation)) { + qDebug() << "Loading applications from" << dirPath; + QDir applicationsDir(dirPath); + + for (const QFileInfo &file : + applicationsDir.entryInfoList(QStringList("*.desktop"))) { + loadDesktopFile(file); + } + } + + // Check that we shit with multiple .desktop files, but some nodisplay files + for (const QString &appId : m_supportedMimetypes.keys()) { + if (!m_desktopFileNames.contains(appId)) { + qWarning() + << appId + << "does not have an associated desktop file!"; + continue; + } + + if (m_applicationNames[appId].isEmpty()) { + qWarning() << "Missing name" << appId; + m_applicationNames[appId] = appId; + } + } + + // Preload up front, so it doesn't get sluggish when selecting applications + // supporting a lot + const QIcon unknownIcon = QIcon::fromTheme("unknown"); + + // TODO: check if QT_QPA_PLATFORMTHEME is set to plasma or sandsmark, + // if so just use the functioning QIcon::fromTheme() + // We do this manually because non-Plasma-platforms icon loading is extremely + // slow (I blame GTK and its crappy icon cache) + for (const QString &searchPath : + (QIcon::themeSearchPaths() + QIcon::fallbackSearchPaths())) { + loadIcons(searchPath + QIcon::themeName()); + loadIcons(searchPath); + } + + for (const QString &mimetypeName : m_supportedMimetypes.values()) { + if (m_mimeTypeIcons.contains(mimetypeName)) { + continue; + } + const QMimeType mimetype = + m_mimeDb.mimeTypeForName(mimetypeName); + + QString iconName = mimetype.iconName(); + QIcon icon(m_iconPaths.value(iconName)); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + int split = iconName.lastIndexOf('+'); + if (split != -1) { + iconName.truncate(split); + icon = QIcon(m_iconPaths.value(iconName)); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + } + split = iconName.lastIndexOf('-'); + if (split != -1) { + iconName.truncate(split); + icon = QIcon(m_iconPaths.value(iconName)); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + } + icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + + m_mimeTypeIcons[mimetypeName] = unknownIcon; + } + + QHBoxLayout *mainLayout = new QHBoxLayout; + setLayout(mainLayout); + m_applicationList = new QTreeWidget; + mainLayout->addWidget(m_applicationList); + + QGridLayout *rightLayout = new QGridLayout; + mainLayout->addLayout(rightLayout); + + m_setDefaultButton = new QPushButton( + tr("Set as default application for these file types")); + m_setDefaultButton->setEnabled(false); + + m_mimetypeList = new QListWidget; + m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); + + rightLayout->addWidget(m_mimetypeList); + rightLayout->addWidget(m_setDefaultButton); + + QStringList types = m_applications.keys(); + std::sort(types.begin(), types.end()); + for (const QString &type : types) { + QTreeWidgetItem *typeItem = + new QTreeWidgetItem(QStringList(type)); + QStringList applications = m_applications[type].values(); + std::sort(applications.begin(), applications.end(), + [=](const QString &a, const QString &b) { + return m_applicationNames[a] < + m_applicationNames[b]; + }); + for (const QString &application : applications) { + QTreeWidgetItem *appItem = new QTreeWidgetItem( + QStringList(m_applicationNames[application])); + appItem->setData(0, Qt::UserRole, application); + appItem->setIcon( + 0, QIcon::fromTheme( + m_applicationIcons[application])); + typeItem->addChild(appItem); + } + m_applicationList->addTopLevelItem(typeItem); + } + m_applicationList->setHeaderHidden(true); + + connect(m_applicationList, &QTreeWidget::itemSelectionChanged, this, + &Widget::onMimetypeSelected); + connect(m_setDefaultButton, &QPushButton::clicked, this, + &Widget::onSetDefaultClicked); } Widget::~Widget() { - } void Widget::onMimetypeSelected() { - m_setDefaultButton->setEnabled(false); - m_mimetypeList->clear(); - - QList selectedItems = m_applicationList->selectedItems(); - if (selectedItems.count() != 1) { - return; - } - - const QTreeWidgetItem *item = selectedItems.first(); - if (!item->parent()) { - return; - } - - const QString mimetypeGroup = item->parent()->text(0); - const QString application = item->data(0, Qt::UserRole).toString(); - - QStringList supported = m_supportedMimetypes.values(application); - - // E. g. kwrite and kate only indicate support for "text/plain", but - // they're nice for things like c source files. - QSet secondary; - const QStringList currentSupported = m_supportedMimetypes.values(application); - for (const QString &mimetype : currentSupported) { - for (const QString &child : m_childMimeTypes.values(mimetype)) { - supported.append(child); - secondary.insert(child); - } - } - supported.removeDuplicates(); - - for (const QString &supportedMime : supported) { - if (!supportedMime.startsWith(mimetypeGroup)) { - continue; - } - const QMimeType mimetype = m_mimeDb.mimeTypeForName(supportedMime); - const QString mimeName = mimetype.name(); - QString name = mimetype.filterString().trimmed(); - if (name.isEmpty()) { - name = mimetype.comment().trimmed(); - } - if (name.isEmpty()) { - name = mimeName; - } else { - name += '\n' + mimeName; - } - QListWidgetItem *item = new QListWidgetItem(name); - item->setData(Qt::UserRole, mimeName); - item->setIcon(m_mimeTypeIcons[supportedMime]); - m_mimetypeList->addItem(item); - item->setSelected(!secondary.contains(mimeName)); - } - - m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); + m_setDefaultButton->setEnabled(false); + m_mimetypeList->clear(); + + QList selectedItems = + m_applicationList->selectedItems(); + if (selectedItems.count() != 1) { + return; + } + + const QTreeWidgetItem *item = selectedItems.first(); + if (!item->parent()) { + return; + } + + const QString mimetypeGroup = item->parent()->text(0); + const QString application = item->data(0, Qt::UserRole).toString(); + + QStringList supported = m_supportedMimetypes.values(application); + + // E. g. kwrite and kate only indicate support for "text/plain", but + // they're nice for things like c source files. + QSet secondary; + const QStringList currentSupported = + m_supportedMimetypes.values(application); + for (const QString &mimetype : currentSupported) { + for (const QString &child : m_childMimeTypes.values(mimetype)) { + supported.append(child); + secondary.insert(child); + } + } + supported.removeDuplicates(); + + for (const QString &supportedMime : supported) { + if (!supportedMime.startsWith(mimetypeGroup)) { + continue; + } + const QMimeType mimetype = + m_mimeDb.mimeTypeForName(supportedMime); + const QString mimeName = mimetype.name(); + QString name = mimetype.filterString().trimmed(); + if (name.isEmpty()) { + name = mimetype.comment().trimmed(); + } + if (name.isEmpty()) { + name = mimeName; + } else { + name += '\n' + mimeName; + } + QListWidgetItem *item = new QListWidgetItem(name); + item->setData(Qt::UserRole, mimeName); + item->setIcon(m_mimeTypeIcons[supportedMime]); + m_mimetypeList->addItem(item); + item->setSelected(!secondary.contains(mimeName)); + } + + m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } void Widget::onSetDefaultClicked() { - QList selectedItems = m_applicationList->selectedItems(); - if (selectedItems.count() != 1) { - return; - } - - const QTreeWidgetItem *item = selectedItems.first(); - if (!item->parent()) { - return; - } - - const QString application = item->data(0, Qt::UserRole).toString(); - if (application.isEmpty()) { - return; - } - - QSet unselected; - QSet selected; - for (int i=0; icount(); i++) { - QListWidgetItem *item = m_mimetypeList->item(i); - const QString name = item->data(Qt::UserRole).toString(); - if (item->isSelected()) { - selected.insert(name); - } else { - unselected.insert(name); - } - } - - setDefault(application, selected, unselected); + QList selectedItems = + m_applicationList->selectedItems(); + if (selectedItems.count() != 1) { + return; + } + + const QTreeWidgetItem *item = selectedItems.first(); + if (!item->parent()) { + return; + } + + const QString application = item->data(0, Qt::UserRole).toString(); + if (application.isEmpty()) { + return; + } + + QSet unselected; + QSet selected; + for (int i = 0; i < m_mimetypeList->count(); i++) { + QListWidgetItem *item = m_mimetypeList->item(i); + const QString name = item->data(Qt::UserRole).toString(); + if (item->isSelected()) { + selected.insert(name); + } else { + unselected.insert(name); + } + } + + setDefault(application, selected, unselected); } void Widget::loadDesktopFile(const QFileInfo &fileInfo) { - // Ugliest implementation of .desktop file reading ever - - QFile file(fileInfo.absoluteFilePath()); - if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to open" << fileInfo.fileName(); - return; - } - - QStringList mimetypes; - QString appName; - QString appId = fileInfo.fileName(); - QString iconName; - - bool inCorrectGroup = false; - bool noDisplay = false; - - while (!file.atEnd()) { - QString line = file.readLine().simplified(); - - if (line.startsWith('[')) { - inCorrectGroup = (line == "[Desktop Entry]"); - continue; - } - - if (!inCorrectGroup) { - continue; - } - - if (line.startsWith("MimeType")) { - line.remove(0, line.indexOf('=') + 1); - mimetypes = line.split(';', Qt::SkipEmptyParts); - continue; - } - - if (line.startsWith("Name") && !line.contains('[')) { - line.remove(0, line.indexOf('=') + 1); - appName = line; - continue; - } - - if (line.startsWith("Icon")) { - line.remove(0, line.indexOf('=') + 1); - iconName = line; - continue; - } - - if (line.startsWith("Exec")) { - line.remove(0, line.indexOf('=') + 1); - if (line.isEmpty()) { - continue; - } - QStringList parts = line.split(' '); - if (parts.first() == "env" && parts.count() > 2) { - line = parts[2]; - } - - appId = line; - continue; - } - - if (line.startsWith("NoDisplay=") && line.contains("true", Qt::CaseInsensitive)) { - noDisplay = true; - } - } - - if (!iconName.isEmpty() && m_applicationIcons[appId].isEmpty()) { - m_applicationIcons[appId] = iconName; - } - - // If an application has a .desktop file without NoDisplay use that, otherwise use one of the ones with NoDisplay anyways - if (!noDisplay || !m_desktopFileNames.contains(appId)) { - m_desktopFileNames[appId] = fileInfo.fileName(); - } - - if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { - m_applicationNames[appId] = appName; - } - - if (mimetypes.isEmpty()) { - return; - } - - const QMimeType octetStream = m_mimeDb.mimeTypeForName("application/octet-stream"); - for (const QString &readMimeName : mimetypes) { - // Resolve aliases etc - const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); - if (!mimetype.isValid()) { - continue; - } - - const QString mimetypeName = mimetype.name(); - for (const QString &parent : mimetype.parentMimeTypes()) { - if (parent == "application/octet-stream") { - break; - } - m_childMimeTypes.insert(parent, mimetypeName); - } - if (m_supportedMimetypes.contains(appId, mimetypeName)) { - continue; - } - - const QStringList parts = mimetypeName.split('/'); - if (parts.count() != 2) { - continue; - } - - const QString type = parts[0].trimmed(); - - m_applications[type].insert(appId); - m_supportedMimetypes.insert(appId, mimetypeName); - } + // Ugliest implementation of .desktop file reading ever + + QFile file(fileInfo.absoluteFilePath()); + if (!file.open(QIODevice::ReadOnly)) { + qDebug() << "Failed to open" << fileInfo.fileName(); + return; + } + + QStringList mimetypes; + QString appName; + QString appId = fileInfo.fileName(); + QString iconName; + + bool inCorrectGroup = false; + bool noDisplay = false; + + while (!file.atEnd()) { + QString line = file.readLine().simplified(); + + if (line.startsWith('[')) { + inCorrectGroup = (line == "[Desktop Entry]"); + continue; + } + + if (!inCorrectGroup) { + continue; + } + + if (line.startsWith("MimeType")) { + line.remove(0, line.indexOf('=') + 1); + mimetypes = line.split(';', Qt::SkipEmptyParts); + continue; + } + + if (line.startsWith("Name") && !line.contains('[')) { + line.remove(0, line.indexOf('=') + 1); + appName = line; + continue; + } + + if (line.startsWith("Icon")) { + line.remove(0, line.indexOf('=') + 1); + iconName = line; + continue; + } + + if (line.startsWith("Exec")) { + line.remove(0, line.indexOf('=') + 1); + if (line.isEmpty()) { + continue; + } + QStringList parts = line.split(' '); + if (parts.first() == "env" && parts.count() > 2) { + line = parts[2]; + } + + appId = line; + continue; + } + + if (line.startsWith("NoDisplay=") && + line.contains("true", Qt::CaseInsensitive)) { + noDisplay = true; + } + } + + if (!iconName.isEmpty() && m_applicationIcons[appId].isEmpty()) { + m_applicationIcons[appId] = iconName; + } + + // If an application has a .desktop file without NoDisplay use that, otherwise + // use one of the ones with NoDisplay anyways + if (!noDisplay || !m_desktopFileNames.contains(appId)) { + m_desktopFileNames[appId] = fileInfo.fileName(); + } + + if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { + m_applicationNames[appId] = appName; + } + + if (mimetypes.isEmpty()) { + return; + } + + const QMimeType octetStream = + m_mimeDb.mimeTypeForName("application/octet-stream"); + for (const QString &readMimeName : mimetypes) { + // Resolve aliases etc + const QMimeType mimetype = + m_mimeDb.mimeTypeForName(readMimeName.trimmed()); + if (!mimetype.isValid()) { + continue; + } + + const QString mimetypeName = mimetype.name(); + for (const QString &parent : mimetype.parentMimeTypes()) { + if (parent == "application/octet-stream") { + break; + } + m_childMimeTypes.insert(parent, mimetypeName); + } + if (m_supportedMimetypes.contains(appId, mimetypeName)) { + continue; + } + + const QStringList parts = mimetypeName.split('/'); + if (parts.count() != 2) { + continue; + } + + const QString type = parts[0].trimmed(); + + m_applications[type].insert(appId); + m_supportedMimetypes.insert(appId, mimetypeName); + } } -void Widget::setDefault(const QString &appName, const QSet &mimetypes, const QSet &unselectedMimetypes) +void Widget::setDefault(const QString &appName, const QSet &mimetypes, + const QSet &unselectedMimetypes) { - QString desktopFile = m_desktopFileNames.value(appName); - if (desktopFile.isEmpty()) { - qWarning() << "invalid" << appName; - return; - } - - const QString filePath = QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).absoluteFilePath("mimeapps.list"); - QFile file(filePath); - - // Read in existing mimeapps.list, skipping the lines for the mimetypes we're updating - QList existingContent; - QList existingAssociations; - if (file.open(QIODevice::ReadOnly)) { - bool inCorrectGroup = false; - while (!file.atEnd()) { - const QByteArray line = file.readLine().trimmed(); - - if (line.isEmpty()) { - continue; - } - - if (line.startsWith('[')) { - inCorrectGroup = (line == "[Default Applications]"); - if (!inCorrectGroup) { - existingContent.append(line); - } - continue; - } - - if (!inCorrectGroup) { - existingContent.append(line); - continue; - } - - if (!line.contains('=')) { - existingAssociations.append(line); - continue; - } - - const QString mimetype = m_mimeDb.mimeTypeForName(line.split('=').first().trimmed()).name(); - if (!mimetypes.contains(mimetype) && !unselectedMimetypes.contains(mimetype)) { - existingAssociations.append(line); - } - } - - file.close(); - } else { - qDebug() << "Unable to open file for reading"; - } - - if (!file.open(QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Failed to store settings"), file.errorString()); - return; - } - - for (const QByteArray &line : existingContent) { - file.write(line + '\n'); - } - file.write("\n[Default Applications]\n"); - for (const QByteArray &line : existingAssociations) { - file.write(line + '\n'); - } - - for (const QString &mimetype : mimetypes) { - file.write(QString(mimetype + '=' + m_desktopFileNames[appName] + '\n').toUtf8()); - } - - return; + QString desktopFile = m_desktopFileNames.value(appName); + if (desktopFile.isEmpty()) { + qWarning() << "invalid" << appName; + return; + } + + const QString filePath = QDir(QStandardPaths::writableLocation( + QStandardPaths::ConfigLocation)) + .absoluteFilePath("mimeapps.list"); + QFile file(filePath); + + // Read in existing mimeapps.list, skipping the lines for the mimetypes we're + // updating + QList existingContent; + QList existingAssociations; + if (file.open(QIODevice::ReadOnly)) { + bool inCorrectGroup = false; + while (!file.atEnd()) { + const QByteArray line = file.readLine().trimmed(); + + if (line.isEmpty()) { + continue; + } + + if (line.startsWith('[')) { + inCorrectGroup = + (line == "[Default Applications]"); + if (!inCorrectGroup) { + existingContent.append(line); + } + continue; + } + + if (!inCorrectGroup) { + existingContent.append(line); + continue; + } + + if (!line.contains('=')) { + existingAssociations.append(line); + continue; + } + + const QString mimetype = + m_mimeDb.mimeTypeForName(line.split('=') + .first() + .trimmed()) + .name(); + if (!mimetypes.contains(mimetype) && + !unselectedMimetypes.contains(mimetype)) { + existingAssociations.append(line); + } + } + + file.close(); + } else { + qDebug() << "Unable to open file for reading"; + } + + if (!file.open(QIODevice::WriteOnly)) { + QMessageBox::warning(this, tr("Failed to store settings"), + file.errorString()); + return; + } + + for (const QByteArray &line : existingContent) { + file.write(line + '\n'); + } + file.write("\n[Default Applications]\n"); + for (const QByteArray &line : existingAssociations) { + file.write(line + '\n'); + } + + for (const QString &mimetype : mimetypes) { + file.write(QString(mimetype + '=' + + m_desktopFileNames[appName] + '\n') + .toUtf8()); + } + + return; } void Widget::loadIcons(const QString &path) { - QFileInfo fi(path); - if (!fi.exists() || !fi.isDir()) { - return; - } - // TODO: avoid hardcoding - QStringList imageTypes({"*.svg", "*.svgz", "*.png", "*.xpm"}); - QDirIterator it(path, imageTypes, QDir::Files, QDirIterator::Subdirectories); - - while (it.hasNext()) { - it.next(); - fi = it.fileInfo(); - - const QString name = fi.completeBaseName(); - if (m_iconPaths.contains(name)) { - continue; - } - m_iconPaths[name] = fi.filePath(); - } + QFileInfo fi(path); + if (!fi.exists() || !fi.isDir()) { + return; + } + // TODO: avoid hardcoding + QStringList imageTypes({ "*.svg", "*.svgz", "*.png", "*.xpm" }); + QDirIterator it(path, imageTypes, QDir::Files, + QDirIterator::Subdirectories); + + while (it.hasNext()) { + it.next(); + fi = it.fileInfo(); + + const QString name = fi.completeBaseName(); + if (m_iconPaths.contains(name)) { + continue; + } + m_iconPaths[name] = fi.filePath(); + } } diff --git a/widget.h b/widget.h index e2f23fa..e13a6c1 100644 --- a/widget.h +++ b/widget.h @@ -10,40 +10,40 @@ class QTreeWidget; class QListWidget; class QPushButton; -class Widget : public QWidget -{ - Q_OBJECT +class Widget : public QWidget { + Q_OBJECT public: - Widget(QWidget *parent = nullptr); - ~Widget(); - + Widget(QWidget *parent = nullptr); + ~Widget(); private slots: - void onMimetypeSelected(); - void onSetDefaultClicked(); + void onMimetypeSelected(); + void onSetDefaultClicked(); private: - void loadDesktopFile(const QFileInfo &fileInfo); - void setDefault(const QString &appName, const QSet &mimetypes, const QSet &unselectedMimetypes); - void loadIcons(const QString &path); - - QMultiHash m_supportedMimetypes; - QMultiHash m_childMimeTypes; - QHash> m_applications; - QHash m_applicationIcons; - QHash m_applicationNames; - QHash m_desktopFileNames; - - QHash m_appIdToDesktopFile; - - QHash m_mimeTypeIcons; // for preloading icons, because that's (a bit) slooow - QHash m_iconPaths; - - QTreeWidget *m_applicationList; - QMimeDatabase m_mimeDb; - QListWidget *m_mimetypeList; - QPushButton *m_setDefaultButton; + void loadDesktopFile(const QFileInfo &fileInfo); + void setDefault(const QString &appName, const QSet &mimetypes, + const QSet &unselectedMimetypes); + void loadIcons(const QString &path); + + QMultiHash m_supportedMimetypes; + QMultiHash m_childMimeTypes; + QHash > m_applications; + QHash m_applicationIcons; + QHash m_applicationNames; + QHash m_desktopFileNames; + + QHash m_appIdToDesktopFile; + + QHash + m_mimeTypeIcons; // for preloading icons, because that's (a bit) slooow + QHash m_iconPaths; + + QTreeWidget *m_applicationList; + QMimeDatabase m_mimeDb; + QListWidget *m_mimetypeList; + QPushButton *m_setDefaultButton; }; #endif // WIDGET_H From 0b000ad45bbb7e4ce191f5afe251b1c58919ad11 Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 15:11:53 -0600 Subject: [PATCH 02/38] More formatting --- README.md | 18 ++++++------------ main.cpp | 8 ++++---- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 5d19b7d..deb5ade 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,17 @@ -Select Default Application -========================== +# Select Default Application A very simple application that lets you define default applications on Linux in a sane way. ![screenshot](/screenshot.png) - -How it works ------------- +## How it works Basically it just loads all installed applications by reading their .desktop files, reads the MimeType fields to see what it supports, and updates ~/.config/mimeapps.list with what the user wants. +## Links -Links ------ - - * https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html - * https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html - * https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html - +- https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html +- https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html +- https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html diff --git a/main.cpp b/main.cpp index 90b6d53..67fb9a1 100644 --- a/main.cpp +++ b/main.cpp @@ -3,9 +3,9 @@ int main(int argc, char *argv[]) { - QApplication a(argc, argv); - Widget w; - w.show(); + QApplication a(argc, argv); + Widget w; + w.show(); - return a.exec(); + return a.exec(); } From e1753343771fde7fcf24851837c7d707c4f1dd30 Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 15:27:09 -0600 Subject: [PATCH 03/38] The GPL2 included was modified from the original license text. This should fix that --- COPYING | 39 +++++++++++++++++++-------------------- 1 file changed, 19 insertions(+), 20 deletions(-) diff --git a/COPYING b/COPYING index 70190fb..d159169 100644 --- a/COPYING +++ b/COPYING @@ -1,12 +1,12 @@ - GNU GENERAL PUBLIC LICENSE - Version 2, June 1991 + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 - Copyright (C) 1989, 1991 Free Software Foundation, Inc. - 51 Franklin Steet, Fifth Floor, Boston, MA 02111-1307 USA + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. - Preamble + Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public @@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by -the GNU Library General Public License instead.) You can apply it to +the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not @@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. - - GNU GENERAL PUBLIC LICENSE + + GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains @@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions: License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) - + These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in @@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. - + 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is @@ -225,7 +225,7 @@ impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. - + 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License @@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. - NO WARRANTY + NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN @@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it @@ -303,10 +303,9 @@ the "copyright" line and a pointer to where the full notice is found. 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 Steet, Fifth Floor, Boston, MA 02111-1307 USA - + 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. Also add information on how to contact you by electronic and paper mail. @@ -336,5 +335,5 @@ necessary. Here is a sample; alter the names: This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the -library. If this is what you want to do, use the GNU Library General +library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. From 4f1efa3531a726cd3e5b983b6b12690f3898d55f Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 15:27:24 -0600 Subject: [PATCH 04/38] Add build information --- README.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/README.md b/README.md index deb5ade..ce60d5e 100644 --- a/README.md +++ b/README.md @@ -15,3 +15,12 @@ files, reads the MimeType fields to see what it supports, and updates - https://specifications.freedesktop.org/mime-apps-spec/mime-apps-spec-latest.html - https://specifications.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html - https://specifications.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html + +## Building + +Just run + +``` +qmake +make +``` \ No newline at end of file From 2c2b23b45c4a79a6b8fe1b1d83c89a929de7c57e Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 15:28:02 -0600 Subject: [PATCH 05/38] Change name from a generic 'widget' to a more descriptive 'selectdefaultapplication' --- .gitignore | 5 +++++ main.cpp | 4 ++-- widget.cpp => selectdefaultapplication.cpp | 20 ++++++++++---------- widget.h => selectdefaultapplication.h | 6 +++--- selectdefaultapplication.pro | 4 ++-- 5 files changed, 22 insertions(+), 17 deletions(-) create mode 100644 .gitignore rename widget.cpp => selectdefaultapplication.cpp (95%) rename widget.h => selectdefaultapplication.h (89%) diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd1b99c --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.qmake.stash +Makefile +moc_* +*.o +selectdefaultapplication diff --git a/main.cpp b/main.cpp index 67fb9a1..4cd9adc 100644 --- a/main.cpp +++ b/main.cpp @@ -1,10 +1,10 @@ -#include "widget.h" +#include "selectdefaultapplication.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); - Widget w; + SelectDefaultApplication w; w.show(); return a.exec(); diff --git a/widget.cpp b/selectdefaultapplication.cpp similarity index 95% rename from widget.cpp rename to selectdefaultapplication.cpp index ced5d76..5e0c399 100644 --- a/widget.cpp +++ b/selectdefaultapplication.cpp @@ -1,4 +1,4 @@ -#include "widget.h" +#include "selectdefaultapplication.h" #include #include #include @@ -12,7 +12,7 @@ #include #include -Widget::Widget(QWidget *parent) : QWidget(parent) +SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(parent) { for (const QString &dirPath : QStandardPaths::standardLocations( QStandardPaths::ApplicationsLocation)) { @@ -142,16 +142,16 @@ Widget::Widget(QWidget *parent) : QWidget(parent) m_applicationList->setHeaderHidden(true); connect(m_applicationList, &QTreeWidget::itemSelectionChanged, this, - &Widget::onMimetypeSelected); + &SelectDefaultApplication::onMimetypeSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, - &Widget::onSetDefaultClicked); + &SelectDefaultApplication::onSetDefaultClicked); } -Widget::~Widget() +SelectDefaultApplication::~SelectDefaultApplication() { } -void Widget::onMimetypeSelected() +void SelectDefaultApplication::onMimetypeSelected() { m_setDefaultButton->setEnabled(false); m_mimetypeList->clear(); @@ -211,7 +211,7 @@ void Widget::onMimetypeSelected() m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } -void Widget::onSetDefaultClicked() +void SelectDefaultApplication::onSetDefaultClicked() { QList selectedItems = m_applicationList->selectedItems(); @@ -244,7 +244,7 @@ void Widget::onSetDefaultClicked() setDefault(application, selected, unselected); } -void Widget::loadDesktopFile(const QFileInfo &fileInfo) +void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) { // Ugliest implementation of .desktop file reading ever @@ -363,7 +363,7 @@ void Widget::loadDesktopFile(const QFileInfo &fileInfo) } } -void Widget::setDefault(const QString &appName, const QSet &mimetypes, +void SelectDefaultApplication::setDefault(const QString &appName, const QSet &mimetypes, const QSet &unselectedMimetypes) { QString desktopFile = m_desktopFileNames.value(appName); @@ -448,7 +448,7 @@ void Widget::setDefault(const QString &appName, const QSet &mimetypes, return; } -void Widget::loadIcons(const QString &path) +void SelectDefaultApplication::loadIcons(const QString &path) { QFileInfo fi(path); if (!fi.exists() || !fi.isDir()) { diff --git a/widget.h b/selectdefaultapplication.h similarity index 89% rename from widget.h rename to selectdefaultapplication.h index e13a6c1..56a32ef 100644 --- a/widget.h +++ b/selectdefaultapplication.h @@ -10,12 +10,12 @@ class QTreeWidget; class QListWidget; class QPushButton; -class Widget : public QWidget { +class SelectDefaultApplication : public QWidget { Q_OBJECT public: - Widget(QWidget *parent = nullptr); - ~Widget(); + SelectDefaultApplication(QWidget *parent = nullptr); + ~SelectDefaultApplication(); private slots: void onMimetypeSelected(); diff --git a/selectdefaultapplication.pro b/selectdefaultapplication.pro index 41558db..ea28c7a 100644 --- a/selectdefaultapplication.pro +++ b/selectdefaultapplication.pro @@ -25,7 +25,7 @@ DEFINES += QT_DEPRECATED_WARNINGS SOURCES += \ main.cpp \ - widget.cpp + selectdefaultapplication.cpp HEADERS += \ - widget.h + selectdefaultapplication.h From 037f71720a5eae6b8fa289621f160e06c8dab563 Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 15:58:25 -0600 Subject: [PATCH 06/38] Don't waste CPU and IO reading desktop files after we finish with the [Desktop Entry] --- selectdefaultapplication.cpp | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 5e0c399..2f01bfe 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -259,19 +259,17 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) QString appId = fileInfo.fileName(); QString iconName; - bool inCorrectGroup = false; bool noDisplay = false; while (!file.atEnd()) { QString line = file.readLine().simplified(); if (line.startsWith('[')) { - inCorrectGroup = (line == "[Desktop Entry]"); - continue; - } - - if (!inCorrectGroup) { - continue; + if (line == "[Desktop Entry]") { + continue; + } + // Multiple groups may not have the same name, and [Desktop Entry] must be the first group. So we are done otherwise + break; } if (line.startsWith("MimeType")) { From 81fdc14e0175d61823dfb2dcef5f5d8471dbe98c Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 18:11:28 -0600 Subject: [PATCH 07/38] Add some things I'm unsure about --- selectdefaultapplication.cpp | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 2f01bfe..e29fa6e 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -25,6 +25,9 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa } } +/* +What? +*/ // Check that we shit with multiple .desktop files, but some nodisplay files for (const QString &appId : m_supportedMimetypes.keys()) { if (!m_desktopFileNames.contains(appId)) { @@ -254,9 +257,11 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) return; } + // The filename of the desktop file as a relative path from the ApplicationLocation directory it is in. + QString appId = fileInfo.fileName(); + // The mimetypes the application can support QStringList mimetypes; QString appName; - QString appId = fileInfo.fileName(); QString iconName; bool noDisplay = false; @@ -278,18 +283,27 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) continue; } + // LocaleStrings and IconStrings may have localized values. We don't want to display those + // TODO technically '[' can appear in the value of a Key. Extract this logic into a helper function + // Additionally Values can start with spaces which should be ignored because FreeDesktop people are dumb if (line.startsWith("Name") && !line.contains('[')) { line.remove(0, line.indexOf('=') + 1); appName = line; continue; } - if (line.startsWith("Icon")) { + if (line.startsWith("Icon") && !line.contains('[')) { line.remove(0, line.indexOf('=') + 1); iconName = line; continue; } +/* +This has lots of problems, starting with the fact that `Exec` may not be the name of the program and not getting better from there +I assume it is done for a reason, probably Okular having 10_000_000_000 desktop files for itself. +Though the original values actually don't seem like any would cause problems based on the inserted debug print below. +Even if this can't be completely removed, it is much better to use the Name key for this or something + if (line.startsWith("Exec")) { line.remove(0, line.indexOf('=') + 1); if (line.isEmpty()) { @@ -299,26 +313,37 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) if (parts.first() == "env" && parts.count() > 2) { line = parts[2]; } +qDebug() << "Updating appId for " << appId << " to " << line; appId = line; continue; } +*/ + +/* +I don't think we should ignore entries that have NoDisplay at all. The specification says regarding NoDisplay entries: +NoDisplay ... can be useful to e.g. associate this application with MIME types ... without having a menu entry for it +If a different desktop file has the same id somehow? then intended behavior should be to add the NoDisplay one's mimetypes to the Display one's, not ignore the NoDisplay one if (line.startsWith("NoDisplay=") && line.contains("true", Qt::CaseInsensitive)) { noDisplay = true; } +*/ } if (!iconName.isEmpty() && m_applicationIcons[appId].isEmpty()) { m_applicationIcons[appId] = iconName; } +/* +See previous comment // If an application has a .desktop file without NoDisplay use that, otherwise // use one of the ones with NoDisplay anyways if (!noDisplay || !m_desktopFileNames.contains(appId)) { m_desktopFileNames[appId] = fileInfo.fileName(); } +*/ if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { m_applicationNames[appId] = appName; From e873d4a012e52388a558c133c8dba0927db83d08 Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 22:17:06 -0600 Subject: [PATCH 08/38] Comment out more useless crap --- selectdefaultapplication.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index e29fa6e..2dbe1ac 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -30,12 +30,15 @@ What? */ // Check that we shit with multiple .desktop files, but some nodisplay files for (const QString &appId : m_supportedMimetypes.keys()) { +/* +This is impossible now that app ids don't come from random shit in their Exec key if (!m_desktopFileNames.contains(appId)) { qWarning() << appId << "does not have an associated desktop file!"; continue; } +*/ if (m_applicationNames[appId].isEmpty()) { qWarning() << "Missing name" << appId; @@ -264,7 +267,10 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) QString appName; QString iconName; +/* +Not used anymore bool noDisplay = false; +*/ while (!file.atEnd()) { QString line = file.readLine().simplified(); From 17dc8bda1ee2072c2e8d714320e8b3921b426947 Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 22:19:21 -0600 Subject: [PATCH 09/38] Add comments --- selectdefaultapplication.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 2dbe1ac..99bd936 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -32,6 +32,7 @@ What? for (const QString &appId : m_supportedMimetypes.keys()) { /* This is impossible now that app ids don't come from random shit in their Exec key + if (!m_desktopFileNames.contains(appId)) { qWarning() << appId @@ -40,6 +41,9 @@ This is impossible now that app ids don't come from random shit in their Exec ke } */ +/* +This should be impossible, but do more thinking +*/ if (m_applicationNames[appId].isEmpty()) { qWarning() << "Missing name" << appId; m_applicationNames[appId] = appId; From 958dc11210bc1d1016480ca991660f71defc91e5 Mon Sep 17 00:00:00 2001 From: magnus Date: Wed, 29 Dec 2021 23:52:40 -0600 Subject: [PATCH 10/38] This is negative quality code --- selectdefaultapplication.cpp | 38 ++++++++++++++++++++++++++++++++++-- selectdefaultapplication.h | 2 +- selectdefaultapplication.pro | 1 - 3 files changed, 37 insertions(+), 4 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 99bd936..588bedf 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -64,6 +64,7 @@ This should be impossible, but do more thinking loadIcons(searchPath); } + // Set m_mimeTypeIcons[mimetypeName] to an appropriate icon for (const QString &mimetypeName : m_supportedMimetypes.values()) { if (m_mimeTypeIcons.contains(mimetypeName)) { continue; @@ -152,7 +153,7 @@ This should be impossible, but do more thinking m_applicationList->setHeaderHidden(true); connect(m_applicationList, &QTreeWidget::itemSelectionChanged, this, - &SelectDefaultApplication::onMimetypeSelected); + &SelectDefaultApplication::onApplicationSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); } @@ -161,7 +162,17 @@ SelectDefaultApplication::~SelectDefaultApplication() { } +/* +Actually is called when you select an APPLICATION +Holy shit this is negative quality code + +Populates the right side of the screen. Selects all the mimetypes that application can natively support +TODO distinguish between mimetypes the application currently is the default of, mimetypes the application natively supports, children of the application's supported types +Currently only distinguishes between the latter two + void SelectDefaultApplication::onMimetypeSelected() +*/ +void SelectDefaultApplication::onApplicationSelected() { m_setDefaultButton->setEnabled(false); m_mimetypeList->clear(); @@ -264,7 +275,7 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) return; } - // The filename of the desktop file as a relative path from the ApplicationLocation directory it is in. + // The filename of the desktop file QString appId = fileInfo.fileName(); // The mimetypes the application can support QStringList mimetypes; @@ -356,6 +367,18 @@ See previous comment */ if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { +/* +See how often collisions occur +*/ +for (QString otherAppId : m_applicationNames.keys()) { + if (m_applicationNames[otherAppId] == appName) { + qDebug() << "Apps " << appId << " and " << otherAppId << " share name " << appName; + } +} +/* +Based on this, it seems necessary to group mimetypes by application name, rather than id. +A refactor is required +*/ m_applicationNames[appId] = appName; } @@ -363,8 +386,13 @@ See previous comment return; } +/* +Apparently compilers these days literally cannot tell when a variable is not used or something, when compiling with -Wall -Werror on +What the fuck + const QMimeType octetStream = m_mimeDb.mimeTypeForName("application/octet-stream"); +*/ for (const QString &readMimeName : mimetypes) { // Resolve aliases etc const QMimeType mimetype = @@ -373,11 +401,17 @@ See previous comment continue; } +/* +I don't see what the parentMimeTypes() stuff is for +As far as I can tell it just makes it seem like we support opening .zips if we have a oxps viewer or .gzips if we have a .gzpdf viewer +Which is clearly untrue +*/ const QString mimetypeName = mimetype.name(); for (const QString &parent : mimetype.parentMimeTypes()) { if (parent == "application/octet-stream") { break; } +qDebug() << "child mime types: " << parent << " to " << mimetypeName; m_childMimeTypes.insert(parent, mimetypeName); } if (m_supportedMimetypes.contains(appId, mimetypeName)) { diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 56a32ef..c2e9739 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -18,7 +18,7 @@ class SelectDefaultApplication : public QWidget { ~SelectDefaultApplication(); private slots: - void onMimetypeSelected(); + void onApplicationSelected(); void onSetDefaultClicked(); private: diff --git a/selectdefaultapplication.pro b/selectdefaultapplication.pro index ea28c7a..8657030 100644 --- a/selectdefaultapplication.pro +++ b/selectdefaultapplication.pro @@ -22,7 +22,6 @@ DEFINES += QT_DEPRECATED_WARNINGS # You can also select to disable deprecated APIs only up to a certain version of Qt. #DEFINES += QT_DISABLE_DEPRECATED_BEFORE=0x060000 # disables all the APIs deprecated before Qt 6.0.0 - SOURCES += \ main.cpp \ selectdefaultapplication.cpp From 059b881d08803290a72215fcf44205c377f59080 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 00:49:42 -0600 Subject: [PATCH 11/38] Go through another function adding comments, find a bug --- selectdefaultapplication.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 588bedf..924c578 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -241,6 +241,9 @@ void SelectDefaultApplication::onSetDefaultClicked() } const QTreeWidgetItem *item = selectedItems.first(); +/* +How could this possibly happen? +*/ if (!item->parent()) { return; } @@ -401,17 +404,11 @@ What the fuck continue; } -/* -I don't see what the parentMimeTypes() stuff is for -As far as I can tell it just makes it seem like we support opening .zips if we have a oxps viewer or .gzips if we have a .gzpdf viewer -Which is clearly untrue -*/ const QString mimetypeName = mimetype.name(); for (const QString &parent : mimetype.parentMimeTypes()) { if (parent == "application/octet-stream") { break; } -qDebug() << "child mime types: " << parent << " to " << mimetypeName; m_childMimeTypes.insert(parent, mimetypeName); } if (m_supportedMimetypes.contains(appId, mimetypeName)) { @@ -433,7 +430,12 @@ qDebug() << "child mime types: " << parent << " to " << mimetypeName; void SelectDefaultApplication::setDefault(const QString &appName, const QSet &mimetypes, const QSet &unselectedMimetypes) { +/* +TODO we will need to associate both a mimetype and a filename to an appName (which will really be an appName then and not appId), but for now appName is the desktopFile + QString desktopFile = m_desktopFileNames.value(appName); +*/ + const QString &desktopFile = appName; if (desktopFile.isEmpty()) { qWarning() << "invalid" << appName; return; @@ -476,6 +478,9 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet Date: Thu, 30 Dec 2021 00:51:02 -0600 Subject: [PATCH 12/38] Make documentation of plan correct --- selectdefaultapplication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 924c578..2859c90 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -431,7 +431,7 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet &unselectedMimetypes) { /* -TODO we will need to associate both a mimetype and a filename to an appName (which will really be an appName then and not appId), but for now appName is the desktopFile +TODO we will need to associate both a mimetype and an appName (which will really be an appName then and not appId) to index a desktopFile, but for now appName is just the desktopFile QString desktopFile = m_desktopFileNames.value(appName); */ From 2407075bf14d1b7cbc91d827eeac4624c56be5f3 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 01:51:42 -0600 Subject: [PATCH 13/38] Changing everything... --- selectdefaultapplication.cpp | 78 +++++++++++++++++++++--------------- selectdefaultapplication.h | 14 +++++-- 2 files changed, 56 insertions(+), 36 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 2859c90..7a154f6 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -29,7 +29,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa What? */ // Check that we shit with multiple .desktop files, but some nodisplay files - for (const QString &appId : m_supportedMimetypes.keys()) { +// for (const QString &appId : m_supportedMimetypes.keys()) { /* This is impossible now that app ids don't come from random shit in their Exec key @@ -44,13 +44,15 @@ This is impossible now that app ids don't come from random shit in their Exec ke /* This should be impossible, but do more thinking */ +/* if (m_applicationNames[appId].isEmpty()) { qWarning() << "Missing name" << appId; m_applicationNames[appId] = appId; } - } +*/ +// } - // Preload up front, so it doesn't get sluggish when selecting applications + // Preload icons up front, so it doesn't get sluggish when selecting applications // supporting a lot const QIcon unknownIcon = QIcon::fromTheme("unknown"); @@ -110,13 +112,10 @@ This should be impossible, but do more thinking m_mimeTypeIcons[mimetypeName] = unknownIcon; } - QHBoxLayout *mainLayout = new QHBoxLayout; - setLayout(mainLayout); - m_applicationList = new QTreeWidget; - mainLayout->addWidget(m_applicationList); - - QGridLayout *rightLayout = new QGridLayout; - mainLayout->addLayout(rightLayout); + m_applicationList = new QListWidget; + m_applicationList->setSelectionMode(QAbstractItemView::SingleSelection); +// TODO allow user to search for applications + populateApplicationList(""); m_setDefaultButton = new QPushButton( tr("Set as default application for these file types")); @@ -125,9 +124,18 @@ This should be impossible, but do more thinking m_mimetypeList = new QListWidget; m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); + QGridLayout *rightLayout = new QGridLayout; rightLayout->addWidget(m_mimetypeList); rightLayout->addWidget(m_setDefaultButton); + QHBoxLayout *mainLayout = new QHBoxLayout; + setLayout(mainLayout); + mainLayout->addWidget(m_applicationList); + mainLayout->addLayout(rightLayout); + +// Populate the left listlayout with applications +// Moved to up with its initialization in the refactor +/* QStringList types = m_applications.keys(); std::sort(types.begin(), types.end()); for (const QString &type : types) { @@ -151,8 +159,9 @@ This should be impossible, but do more thinking m_applicationList->addTopLevelItem(typeItem); } m_applicationList->setHeaderHidden(true); +*/ - connect(m_applicationList, &QTreeWidget::itemSelectionChanged, this, + connect(m_applicationList, &QListWidget::itemSelectionChanged, this, &SelectDefaultApplication::onApplicationSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); @@ -177,19 +186,17 @@ void SelectDefaultApplication::onApplicationSelected() m_setDefaultButton->setEnabled(false); m_mimetypeList->clear(); - QList selectedItems = + QList selectedItems = m_applicationList->selectedItems(); if (selectedItems.count() != 1) { return; } - const QTreeWidgetItem *item = selectedItems.first(); - if (!item->parent()) { - return; - } + const QListWidgetItem *item = selectedItems.first(); - const QString mimetypeGroup = item->parent()->text(0); - const QString application = item->data(0, Qt::UserRole).toString(); + //const QString mimetypeGroup = item->parent()->text(0); + //const QString application = item->data(0, Qt::UserRole).toString(); + const QString application = item->data(0).toString(); QStringList supported = m_supportedMimetypes.values(application); @@ -207,9 +214,13 @@ void SelectDefaultApplication::onApplicationSelected() supported.removeDuplicates(); for (const QString &supportedMime : supported) { +/* +TODO allow the user to check different mimetype groups to see only applications that affect those groups, and only associations in those groups + if (!supportedMime.startsWith(mimetypeGroup)) { continue; } +*/ const QMimeType mimetype = m_mimeDb.mimeTypeForName(supportedMime); const QString mimeName = mimetype.name(); @@ -282,6 +293,8 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) QString appId = fileInfo.fileName(); // The mimetypes the application can support QStringList mimetypes; + // The name of the application in its desktop entry + // Used as the primary key with which associations are made QString appName; QString iconName; @@ -369,21 +382,23 @@ See previous comment } */ - if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { +// if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { /* See how often collisions occur */ +/* for (QString otherAppId : m_applicationNames.keys()) { if (m_applicationNames[otherAppId] == appName) { qDebug() << "Apps " << appId << " and " << otherAppId << " share name " << appName; } } +*/ /* Based on this, it seems necessary to group mimetypes by application name, rather than id. A refactor is required */ - m_applicationNames[appId] = appName; - } +// m_applicationNames[appId] = appName; +// } if (mimetypes.isEmpty()) { return; @@ -480,6 +495,7 @@ TODO we will need to associate both a mimetype and an appName (which will really /* Doesn't appear to validate that it is a mimetype, which isn't good practice I think +Nevermind behaves correctly */ const QString mimetype = m_mimeDb.mimeTypeForName(line.split('=') @@ -502,7 +518,7 @@ Later...: Yeah I tested it and this is a bug qDebug() << "Unable to open file for reading"; /* If we can't open the file for reading, we better stop before opening for writing and deleting it -Unless we check and the file isn't there at all +Unless we check explicitely and the file isn't there at all */ } @@ -525,29 +541,27 @@ Unless we check and the file isn't there at all desktopFile + '\n') .toUtf8()); } - - return; } void SelectDefaultApplication::loadIcons(const QString &path) { - QFileInfo fi(path); - if (!fi.exists() || !fi.isDir()) { + QFileInfo icon_file(path); + if (!icon_file.exists() || !icon_file.isDir()) { return; } // TODO: avoid hardcoding QStringList imageTypes({ "*.svg", "*.svgz", "*.png", "*.xpm" }); - QDirIterator it(path, imageTypes, QDir::Files, + QDirIterator iter(path, imageTypes, QDir::Files, QDirIterator::Subdirectories); - while (it.hasNext()) { - it.next(); - fi = it.fileInfo(); + while (iter.hasNext()) { + iter.next(); + icon_file = iter.fileInfo(); - const QString name = fi.completeBaseName(); + const QString name = icon_file.completeBaseName(); if (m_iconPaths.contains(name)) { continue; } - m_iconPaths[name] = fi.filePath(); + m_iconPaths[name] = icon_file.filePath(); } } diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index c2e9739..490b67c 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -26,13 +26,19 @@ private slots: void setDefault(const QString &appName, const QSet &mimetypes, const QSet &unselectedMimetypes); void loadIcons(const QString &path); + void populateApplicationList(const QString &filter); + // Should be refactored to only use m_apps() QMultiHash m_supportedMimetypes; + // Don't think this can do the same QMultiHash m_childMimeTypes; - QHash > m_applications; + + // Hashtable of application names to hashtables of mimetypes to .desktop file entries + QHash > m_apps; + // Hashtable of application names to icons QHash m_applicationIcons; - QHash m_applicationNames; - QHash m_desktopFileNames; + //QHash m_applicationNames; + //QHash m_desktopFileNames; QHash m_appIdToDesktopFile; @@ -40,7 +46,7 @@ private slots: m_mimeTypeIcons; // for preloading icons, because that's (a bit) slooow QHash m_iconPaths; - QTreeWidget *m_applicationList; + QListWidget *m_applicationList; QMimeDatabase m_mimeDb; QListWidget *m_mimetypeList; QPushButton *m_setDefaultButton; From 59e659537ff3b1d7614c5a6308ae3633c7745990 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 03:12:16 -0600 Subject: [PATCH 14/38] Big rewrite compiles, probably doesn't work --- selectdefaultapplication.cpp | 124 +++++++++++++++++++++-------------- selectdefaultapplication.h | 5 +- 2 files changed, 76 insertions(+), 53 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 7a154f6..02ae91b 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -245,21 +245,15 @@ TODO allow the user to check different mimetype groups to see only applications void SelectDefaultApplication::onSetDefaultClicked() { - QList selectedItems = + QList selectedItems = m_applicationList->selectedItems(); if (selectedItems.count() != 1) { return; } - const QTreeWidgetItem *item = selectedItems.first(); -/* -How could this possibly happen? -*/ - if (!item->parent()) { - return; - } + const QListWidgetItem *item = selectedItems.first(); - const QString application = item->data(0, Qt::UserRole).toString(); + const QString application = item->data(0).toString(); if (application.isEmpty()) { return; } @@ -290,13 +284,14 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) } // The filename of the desktop file - QString appId = fileInfo.fileName(); + const QString &appFile = fileInfo.fileName(); // The mimetypes the application can support QStringList mimetypes; // The name of the application in its desktop entry // Used as the primary key with which associations are made QString appName; - QString iconName; + // The name of the icon as given in the desktop entry + QString appIcon; /* Not used anymore @@ -304,6 +299,7 @@ Not used anymore */ while (!file.atEnd()) { + // Removes all runs of whitespace, but won't make `Name=` and `Name =` the same QString line = file.readLine().simplified(); if (line.startsWith('[')) { @@ -312,28 +308,21 @@ Not used anymore } // Multiple groups may not have the same name, and [Desktop Entry] must be the first group. So we are done otherwise break; + } else { + // Trim the strings because the '=' can be padded with spaces because FreeDesktop is stupid, even though not a single desktop file on my computer uses that + const QString key = line.section('=', 0, 0).trimmed(); + const QString value = line.section('=', 1).trimmed(); + + if (key == "Name") { + appName = value; + } else if (key == "MimeType") { + mimetypes = value.split(';', Qt::SkipEmptyParts); + } else if (key == "Icon") { + appIcon = value; + } + // Else ignore the key } - - if (line.startsWith("MimeType")) { - line.remove(0, line.indexOf('=') + 1); - mimetypes = line.split(';', Qt::SkipEmptyParts); - continue; - } - - // LocaleStrings and IconStrings may have localized values. We don't want to display those - // TODO technically '[' can appear in the value of a Key. Extract this logic into a helper function - // Additionally Values can start with spaces which should be ignored because FreeDesktop people are dumb - if (line.startsWith("Name") && !line.contains('[')) { - line.remove(0, line.indexOf('=') + 1); - appName = line; - continue; - } - - if (line.startsWith("Icon") && !line.contains('[')) { - line.remove(0, line.indexOf('=') + 1); - iconName = line; - continue; - } + } /* This has lots of problems, starting with the fact that `Exec` may not be the name of the program and not getting better from there @@ -367,12 +356,16 @@ If a different desktop file has the same id somehow? then intended behavior shou noDisplay = true; } */ - } - if (!iconName.isEmpty() && m_applicationIcons[appId].isEmpty()) { - m_applicationIcons[appId] = iconName; + if (!appIcon.isEmpty() && m_applicationIcons[appFile].isEmpty()) { + m_applicationIcons[appName] = appIcon; + } + // Even if mimetypes is empty, set the icon in case a different one isn't + if (mimetypes.isEmpty()) { + return; } + /* See previous comment // If an application has a .desktop file without NoDisplay use that, otherwise @@ -400,10 +393,6 @@ A refactor is required // m_applicationNames[appId] = appName; // } - if (mimetypes.isEmpty()) { - return; - } - /* Apparently compilers these days literally cannot tell when a variable is not used or something, when compiling with -Wall -Werror on What the fuck @@ -416,29 +405,54 @@ What the fuck const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); if (!mimetype.isValid()) { + qDebug() << "In file " << appName << " mimetype " << readMimeName << " is invalid. Ignoring..."; continue; } - const QString mimetypeName = mimetype.name(); + + // Create a database of mimetypes this application is a child of + // So applications that can edit parent mimetypes can also have associations formed to their child mimetypes + // Unless the parent is 'application/octet-stream' because I guess a lot of stuff has that as its parent + // Example: Kate editing text/plain can edit C source code for (const QString &parent : mimetype.parentMimeTypes()) { if (parent == "application/octet-stream") { break; } m_childMimeTypes.insert(parent, mimetypeName); } - if (m_supportedMimetypes.contains(appId, mimetypeName)) { + +/* +use m_apps instead + if (m_supportedMimetypes.contains(appName, mimetypeName)) { + // TODO check to make sure the appFile's are distinct (in case the user copied to .local/share/applications to override it for example) and print the appFiles of both conflicting definitions continue; } +*/ + +/* +TODO use type, I think this can still be deleted though. It needs to go elsewhere const QStringList parts = mimetypeName.split('/'); if (parts.count() != 2) { + qDebug() << "Warning: encountered mimetype " << mimetypeName << " with more than 1 '/' character in " << appFile << " Unsure what to do, skipping..."; continue; } const QString type = parts[0].trimmed(); - - m_applications[type].insert(appId); - m_supportedMimetypes.insert(appId, mimetypeName); +*/ + /* Indexing here creates an empty hashmap if one doesn't exist, so take advantage of that + if (!m_apps.contains(appName)) { m_apps[appName] = QHash(); } + */ + // If we've already got an association for this app from a different desktop file, don't overwrite it because we read highest-priority .desktops first + if (m_apps[appName].contains(mimetypeName)) { + qDebug() << "Info: " << appName << " already handles " << mimetypeName; + continue; + } + m_apps[appName][mimetypeName] = appFile; +/* +Info is contained in m_apps, so use it elsewhere instead of this and remove this +*/ + m_supportedMimetypes.insert(appName, mimetypeName); } } @@ -450,12 +464,6 @@ TODO we will need to associate both a mimetype and an appName (which will really QString desktopFile = m_desktopFileNames.value(appName); */ - const QString &desktopFile = appName; - if (desktopFile.isEmpty()) { - qWarning() << "invalid" << appName; - return; - } - const QString filePath = QDir(QStandardPaths::writableLocation( QStandardPaths::ConfigLocation)) .absoluteFilePath("mimeapps.list"); @@ -538,11 +546,25 @@ Unless we check explicitely and the file isn't there at all for (const QString &mimetype : mimetypes) { file.write(QString(mimetype + '=' + - desktopFile + '\n') + m_apps[appName][mimetype] + '\n') .toUtf8()); } } +void SelectDefaultApplication::populateApplicationList(const QString &filter) { + m_applicationList->clear(); + + QStringList applications = m_apps.keys().filter(filter); + std::sort(applications.begin(), applications.end()); + for (const QString &appName : applications) { + QListWidgetItem *app = new QListWidgetItem(appName); + app->setData(Qt::UserRole, "Some data"); + app->setIcon(QIcon::fromTheme(m_applicationIcons[appName])); + m_applicationList->addItem(app); + app->setSelected(false); + } +} + void SelectDefaultApplication::loadIcons(const QString &path) { QFileInfo icon_file(path); diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 490b67c..e115404 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -29,12 +29,13 @@ private slots: void populateApplicationList(const QString &filter); // Should be refactored to only use m_apps() + // Hashtable of application names to mimetypes QMultiHash m_supportedMimetypes; - // Don't think this can do the same + // Hashtable with keys as parent mime types and values as all children of that mimetype which are encountered QMultiHash m_childMimeTypes; // Hashtable of application names to hashtables of mimetypes to .desktop file entries - QHash > m_apps; + QHash > m_apps; // Hashtable of application names to icons QHash m_applicationIcons; //QHash m_applicationNames; From e76138237542178a4fc717146a73fea62faff958 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 03:59:04 -0600 Subject: [PATCH 15/38] Remove more dead code --- selectdefaultapplication.cpp | 40 ++++++++++++++++++++---------------- selectdefaultapplication.h | 6 ++++-- 2 files changed, 26 insertions(+), 20 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 02ae91b..e74e0ad 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -67,7 +67,8 @@ This should be impossible, but do more thinking } // Set m_mimeTypeIcons[mimetypeName] to an appropriate icon - for (const QString &mimetypeName : m_supportedMimetypes.values()) { + for (const QHash &application_associations : m_apps.values()) { + for (const QString &mimetypeName : application_associations.keys()) { if (m_mimeTypeIcons.contains(mimetypeName)) { continue; } @@ -111,6 +112,7 @@ This should be impossible, but do more thinking m_mimeTypeIcons[mimetypeName] = unknownIcon; } + } m_applicationList = new QListWidget; m_applicationList->setSelectionMode(QAbstractItemView::SingleSelection); @@ -198,22 +200,17 @@ void SelectDefaultApplication::onApplicationSelected() //const QString application = item->data(0, Qt::UserRole).toString(); const QString application = item->data(0).toString(); - QStringList supported = m_supportedMimetypes.values(application); - + const QStringList officiallySupported = + m_apps.value(application).keys(); // E. g. kwrite and kate only indicate support for "text/plain", but // they're nice for things like c source files. - QSet secondary; - const QStringList currentSupported = - m_supportedMimetypes.values(application); - for (const QString &mimetype : currentSupported) { + QSet impliedSupported; + for (const QString &mimetype : officiallySupported) { for (const QString &child : m_childMimeTypes.values(mimetype)) { - supported.append(child); - secondary.insert(child); + impliedSupported.insert(child); } } - supported.removeDuplicates(); - for (const QString &supportedMime : supported) { /* TODO allow the user to check different mimetype groups to see only applications that affect those groups, and only associations in those groups @@ -221,8 +218,18 @@ TODO allow the user to check different mimetype groups to see only applications continue; } */ + for (const QString &mimetype : officiallySupported) { + addToMimetypeList(mimetype, true); + } + for (const QString &mimetype : impliedSupported) { + addToMimetypeList(mimetype, false); + } + + m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); +} +void SelectDefaultApplication::addToMimetypeList(const QString &mimetypeDirtyName, const bool selected) { const QMimeType mimetype = - m_mimeDb.mimeTypeForName(supportedMime); + m_mimeDb.mimeTypeForName(mimetypeDirtyName); const QString mimeName = mimetype.name(); QString name = mimetype.filterString().trimmed(); if (name.isEmpty()) { @@ -235,12 +242,10 @@ TODO allow the user to check different mimetype groups to see only applications } QListWidgetItem *item = new QListWidgetItem(name); item->setData(Qt::UserRole, mimeName); - item->setIcon(m_mimeTypeIcons[supportedMime]); + item->setIcon(m_mimeTypeIcons[mimetypeDirtyName]); m_mimetypeList->addItem(item); - item->setSelected(!secondary.contains(mimeName)); - } + item->setSelected(selected); - m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } void SelectDefaultApplication::onSetDefaultClicked() @@ -452,7 +457,7 @@ TODO use type, I think this can still be deleted though. It needs to go elsewher /* Info is contained in m_apps, so use it elsewhere instead of this and remove this */ - m_supportedMimetypes.insert(appName, mimetypeName); + //m_supportedMimetypes.insert(appName, mimetypeName); } } @@ -558,7 +563,6 @@ void SelectDefaultApplication::populateApplicationList(const QString &filter) { std::sort(applications.begin(), applications.end()); for (const QString &appName : applications) { QListWidgetItem *app = new QListWidgetItem(appName); - app->setData(Qt::UserRole, "Some data"); app->setIcon(QIcon::fromTheme(m_applicationIcons[appName])); m_applicationList->addItem(app); app->setSelected(false); diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index e115404..7f87589 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -27,10 +27,12 @@ private slots: const QSet &unselectedMimetypes); void loadIcons(const QString &path); void populateApplicationList(const QString &filter); + void addToMimetypeList(const QString &mimetypeName, const bool selected); - // Should be refactored to only use m_apps() // Hashtable of application names to mimetypes - QMultiHash m_supportedMimetypes; + // Used because it is difficult to extract the information from m_apps' multiple different hashtables + // JK its not used + //QMultiHash m_supportedMimetypes; // Hashtable with keys as parent mime types and values as all children of that mimetype which are encountered QMultiHash m_childMimeTypes; From 85bcd9c90ec26cafb65503208b7d547de7677e95 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 04:15:36 -0600 Subject: [PATCH 16/38] Fix the bug where existing associations are deleted if we set a different application to be default for some other schemes than said existing association, but said application can handle said association --- selectdefaultapplication.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index e74e0ad..e6cf45e 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -524,6 +524,13 @@ I'm pretty sure if unselectedMimetypes contains mimetype but .second().trimmed() Later...: Yeah I tested it and this is a bug */ + if (unselectedMimetypes.contains(mimetype)) { + const QString handlingAppFile = line.split('=')[1]; + const QString appFile = m_apps[appName][mimetype]; + if (appFile != handlingAppFile) { + existingAssociations.append(line); + } + } } file.close(); From debcb89860c063ad25363a470ff0cbc3809a8bfd Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 04:39:47 -0600 Subject: [PATCH 17/38] Track down why some output messages were being spammed, document why --- selectdefaultapplication.cpp | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index e6cf45e..229a844 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -410,7 +410,8 @@ What the fuck const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); if (!mimetype.isValid()) { - qDebug() << "In file " << appName << " mimetype " << readMimeName << " is invalid. Ignoring..."; + // TODO This happens a TON. Why? + //qDebug() << "In file " << appName << " mimetype " << readMimeName << " is invalid. Ignoring..."; continue; } const QString mimetypeName = mimetype.name(); @@ -434,23 +435,20 @@ use m_apps instead } */ -/* -TODO use type, I think this can still be deleted though. It needs to go elsewhere - - const QStringList parts = mimetypeName.split('/'); - if (parts.count() != 2) { - qDebug() << "Warning: encountered mimetype " << mimetypeName << " with more than 1 '/' character in " << appFile << " Unsure what to do, skipping..."; + if (mimetypeName.count('/') != 1) { + qDebug() << "Warning: encountered mimetype " << mimetypeName << " without exactly 1 '/' character in " << appFile << " Unsure what to do, skipping..."; continue; } +/* +TODO use type, I think this can still be deleted though. It needs to go elsewhere const QString type = parts[0].trimmed(); */ - /* Indexing here creates an empty hashmap if one doesn't exist, so take advantage of that - if (!m_apps.contains(appName)) { m_apps[appName] = QHash(); } - */ + // Indexing in Qt creates a default element if one doesn't exist, so we don't need to explicitely check if m_apps[appName] exists // If we've already got an association for this app from a different desktop file, don't overwrite it because we read highest-priority .desktops first if (m_apps[appName].contains(mimetypeName)) { - qDebug() << "Info: " << appName << " already handles " << mimetypeName; + // Annoyingly, some apps like KDE mobile apps add associations for *the same exact file type* through two different aliases, so this gets spammed a lot. + qDebug() << "Info: " << appName << " already handles " << mimetypeName << " with " << m_apps[appName][mimetypeName] << " so " << appFile << "will be ignored"; continue; } m_apps[appName][mimetypeName] = appFile; From 5a99813da50f179d59a0e15923fec6133a1cdcf6 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 05:12:34 -0600 Subject: [PATCH 18/38] Remove all the crufty comments I had lying around in case I broke something --- .clang-format | 2 +- selectdefaultapplication.cpp | 366 +++++++++-------------------------- 2 files changed, 94 insertions(+), 274 deletions(-) diff --git a/.clang-format b/.clang-format index a7202fe..5491ed9 100644 --- a/.clang-format +++ b/.clang-format @@ -52,7 +52,7 @@ BreakConstructorInitializersBeforeComma: false #BreakConstructorInitializers: BeforeComma # Unknown to clang-format-4.0 BreakAfterJavaFieldAnnotations: false BreakStringLiterals: false -ColumnLimit: 80 +ColumnLimit: 120 CommentPragmas: '^ IWYU pragma:' #CompactNamespaces: false # Unknown to clang-format-4.0 ConstructorInitializerAllOnOneLineOrOnePerLine: false diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 229a844..57f622f 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -14,44 +14,15 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(parent) { - for (const QString &dirPath : QStandardPaths::standardLocations( - QStandardPaths::ApplicationsLocation)) { + for (const QString &dirPath : QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { qDebug() << "Loading applications from" << dirPath; QDir applicationsDir(dirPath); - for (const QFileInfo &file : - applicationsDir.entryInfoList(QStringList("*.desktop"))) { + for (const QFileInfo &file : applicationsDir.entryInfoList(QStringList("*.desktop"))) { loadDesktopFile(file); } } -/* -What? -*/ - // Check that we shit with multiple .desktop files, but some nodisplay files -// for (const QString &appId : m_supportedMimetypes.keys()) { -/* -This is impossible now that app ids don't come from random shit in their Exec key - - if (!m_desktopFileNames.contains(appId)) { - qWarning() - << appId - << "does not have an associated desktop file!"; - continue; - } -*/ - -/* -This should be impossible, but do more thinking -*/ -/* - if (m_applicationNames[appId].isEmpty()) { - qWarning() << "Missing name" << appId; - m_applicationNames[appId] = appId; - } -*/ -// } - // Preload icons up front, so it doesn't get sluggish when selecting applications // supporting a lot const QIcon unknownIcon = QIcon::fromTheme("unknown"); @@ -60,67 +31,64 @@ This should be impossible, but do more thinking // if so just use the functioning QIcon::fromTheme() // We do this manually because non-Plasma-platforms icon loading is extremely // slow (I blame GTK and its crappy icon cache) - for (const QString &searchPath : - (QIcon::themeSearchPaths() + QIcon::fallbackSearchPaths())) { + for (const QString &searchPath : (QIcon::themeSearchPaths() + QIcon::fallbackSearchPaths())) { loadIcons(searchPath + QIcon::themeName()); loadIcons(searchPath); } // Set m_mimeTypeIcons[mimetypeName] to an appropriate icon - for (const QHash &application_associations : m_apps.values()) { - for (const QString &mimetypeName : application_associations.keys()) { - if (m_mimeTypeIcons.contains(mimetypeName)) { - continue; - } - const QMimeType mimetype = - m_mimeDb.mimeTypeForName(mimetypeName); + for (const QHash &application_associations : m_apps.values()) { + for (const QString &mimetypeName : application_associations.keys()) { + if (m_mimeTypeIcons.contains(mimetypeName)) { + continue; + } + const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeName); - QString iconName = mimetype.iconName(); - QIcon icon(m_iconPaths.value(iconName)); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - int split = iconName.lastIndexOf('+'); - if (split != -1) { - iconName.truncate(split); - icon = QIcon(m_iconPaths.value(iconName)); + QString iconName = mimetype.iconName(); + QIcon icon(m_iconPaths.value(iconName)); if (!icon.isNull()) { m_mimeTypeIcons[mimetypeName] = icon; continue; } - } - split = iconName.lastIndexOf('-'); - if (split != -1) { - iconName.truncate(split); - icon = QIcon(m_iconPaths.value(iconName)); + icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + int split = iconName.lastIndexOf('+'); + if (split != -1) { + iconName.truncate(split); + icon = QIcon(m_iconPaths.value(iconName)); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + } + split = iconName.lastIndexOf('-'); + if (split != -1) { + iconName.truncate(split); + icon = QIcon(m_iconPaths.value(iconName)); + if (!icon.isNull()) { + m_mimeTypeIcons[mimetypeName] = icon; + continue; + } + } + icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); if (!icon.isNull()) { m_mimeTypeIcons[mimetypeName] = icon; continue; } - } - icon = QIcon(m_iconPaths.value(mimetype.genericIconName())); - if (!icon.isNull()) { - m_mimeTypeIcons[mimetypeName] = icon; - continue; - } - m_mimeTypeIcons[mimetypeName] = unknownIcon; - } + m_mimeTypeIcons[mimetypeName] = unknownIcon; + } } m_applicationList = new QListWidget; m_applicationList->setSelectionMode(QAbstractItemView::SingleSelection); -// TODO allow user to search for applications + // TODO allow user to search for applications populateApplicationList(""); - m_setDefaultButton = new QPushButton( - tr("Set as default application for these file types")); + m_setDefaultButton = new QPushButton(tr("Set as default application for these file types")); m_setDefaultButton->setEnabled(false); m_mimetypeList = new QListWidget; @@ -135,38 +103,9 @@ This should be impossible, but do more thinking mainLayout->addWidget(m_applicationList); mainLayout->addLayout(rightLayout); -// Populate the left listlayout with applications -// Moved to up with its initialization in the refactor -/* - QStringList types = m_applications.keys(); - std::sort(types.begin(), types.end()); - for (const QString &type : types) { - QTreeWidgetItem *typeItem = - new QTreeWidgetItem(QStringList(type)); - QStringList applications = m_applications[type].values(); - std::sort(applications.begin(), applications.end(), - [=](const QString &a, const QString &b) { - return m_applicationNames[a] < - m_applicationNames[b]; - }); - for (const QString &application : applications) { - QTreeWidgetItem *appItem = new QTreeWidgetItem( - QStringList(m_applicationNames[application])); - appItem->setData(0, Qt::UserRole, application); - appItem->setIcon( - 0, QIcon::fromTheme( - m_applicationIcons[application])); - typeItem->addChild(appItem); - } - m_applicationList->addTopLevelItem(typeItem); - } - m_applicationList->setHeaderHidden(true); -*/ - connect(m_applicationList, &QListWidget::itemSelectionChanged, this, &SelectDefaultApplication::onApplicationSelected); - connect(m_setDefaultButton, &QPushButton::clicked, this, - &SelectDefaultApplication::onSetDefaultClicked); + connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); } SelectDefaultApplication::~SelectDefaultApplication() @@ -174,34 +113,29 @@ SelectDefaultApplication::~SelectDefaultApplication() } /* -Actually is called when you select an APPLICATION -Holy shit this is negative quality code - Populates the right side of the screen. Selects all the mimetypes that application can natively support TODO distinguish between mimetypes the application currently is the default of, mimetypes the application natively supports, children of the application's supported types Currently only distinguishes between the latter two - -void SelectDefaultApplication::onMimetypeSelected() */ void SelectDefaultApplication::onApplicationSelected() { m_setDefaultButton->setEnabled(false); m_mimetypeList->clear(); - QList selectedItems = - m_applicationList->selectedItems(); + QList selectedItems = m_applicationList->selectedItems(); if (selectedItems.count() != 1) { return; } const QListWidgetItem *item = selectedItems.first(); - //const QString mimetypeGroup = item->parent()->text(0); - //const QString application = item->data(0, Qt::UserRole).toString(); const QString application = item->data(0).toString(); - const QStringList officiallySupported = - m_apps.value(application).keys(); + const QStringList officiallySupported = m_apps.value(application).keys(); + + // TODO allow the user to check different mimetype groups to see only applications that affect those groups, and here remove mimetypes not in that group + //if (!supportedMime.startsWith(mimetypeGroup)) { continue; } + // E. g. kwrite and kate only indicate support for "text/plain", but // they're nice for things like c source files. QSet impliedSupported; @@ -211,13 +145,6 @@ void SelectDefaultApplication::onApplicationSelected() } } -/* -TODO allow the user to check different mimetype groups to see only applications that affect those groups, and only associations in those groups - - if (!supportedMime.startsWith(mimetypeGroup)) { - continue; - } -*/ for (const QString &mimetype : officiallySupported) { addToMimetypeList(mimetype, true); } @@ -227,31 +154,29 @@ TODO allow the user to check different mimetype groups to see only applications m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } -void SelectDefaultApplication::addToMimetypeList(const QString &mimetypeDirtyName, const bool selected) { - const QMimeType mimetype = - m_mimeDb.mimeTypeForName(mimetypeDirtyName); - const QString mimeName = mimetype.name(); - QString name = mimetype.filterString().trimmed(); - if (name.isEmpty()) { - name = mimetype.comment().trimmed(); - } - if (name.isEmpty()) { - name = mimeName; - } else { - name += '\n' + mimeName; - } - QListWidgetItem *item = new QListWidgetItem(name); - item->setData(Qt::UserRole, mimeName); - item->setIcon(m_mimeTypeIcons[mimetypeDirtyName]); - m_mimetypeList->addItem(item); - item->setSelected(selected); - +void SelectDefaultApplication::addToMimetypeList(const QString &mimetypeDirtyName, const bool selected) +{ + const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeDirtyName); + const QString mimeName = mimetype.name(); + QString name = mimetype.filterString().trimmed(); + if (name.isEmpty()) { + name = mimetype.comment().trimmed(); + } + if (name.isEmpty()) { + name = mimeName; + } else { + name += '\n' + mimeName; + } + QListWidgetItem *item = new QListWidgetItem(name); + item->setData(Qt::UserRole, mimeName); + item->setIcon(m_mimeTypeIcons[mimetypeDirtyName]); + m_mimetypeList->addItem(item); + item->setSelected(selected); } void SelectDefaultApplication::onSetDefaultClicked() { - QList selectedItems = - m_applicationList->selectedItems(); + QList selectedItems = m_applicationList->selectedItems(); if (selectedItems.count() != 1) { return; } @@ -284,7 +209,7 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) QFile file(fileInfo.absoluteFilePath()); if (!file.open(QIODevice::ReadOnly)) { - qDebug() << "Failed to open" << fileInfo.fileName(); + qWarning() << "Error: Failed to open" << fileInfo.fileName(); return; } @@ -298,11 +223,6 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) // The name of the icon as given in the desktop entry QString appIcon; -/* -Not used anymore - bool noDisplay = false; -*/ - while (!file.atEnd()) { // Removes all runs of whitespace, but won't make `Name=` and `Name =` the same QString line = file.readLine().simplified(); @@ -329,39 +249,6 @@ Not used anymore } } -/* -This has lots of problems, starting with the fact that `Exec` may not be the name of the program and not getting better from there -I assume it is done for a reason, probably Okular having 10_000_000_000 desktop files for itself. -Though the original values actually don't seem like any would cause problems based on the inserted debug print below. -Even if this can't be completely removed, it is much better to use the Name key for this or something - - if (line.startsWith("Exec")) { - line.remove(0, line.indexOf('=') + 1); - if (line.isEmpty()) { - continue; - } - QStringList parts = line.split(' '); - if (parts.first() == "env" && parts.count() > 2) { - line = parts[2]; - } -qDebug() << "Updating appId for " << appId << " to " << line; - - appId = line; - continue; - } -*/ - -/* -I don't think we should ignore entries that have NoDisplay at all. The specification says regarding NoDisplay entries: -NoDisplay ... can be useful to e.g. associate this application with MIME types ... without having a menu entry for it -If a different desktop file has the same id somehow? then intended behavior should be to add the NoDisplay one's mimetypes to the Display one's, not ignore the NoDisplay one - - if (line.startsWith("NoDisplay=") && - line.contains("true", Qt::CaseInsensitive)) { - noDisplay = true; - } -*/ - if (!appIcon.isEmpty() && m_applicationIcons[appFile].isEmpty()) { m_applicationIcons[appName] = appIcon; } @@ -370,45 +257,9 @@ If a different desktop file has the same id somehow? then intended behavior shou return; } - -/* -See previous comment - // If an application has a .desktop file without NoDisplay use that, otherwise - // use one of the ones with NoDisplay anyways - if (!noDisplay || !m_desktopFileNames.contains(appId)) { - m_desktopFileNames[appId] = fileInfo.fileName(); - } -*/ - -// if (!appName.isEmpty() && m_applicationNames[appId].isEmpty()) { -/* -See how often collisions occur -*/ -/* -for (QString otherAppId : m_applicationNames.keys()) { - if (m_applicationNames[otherAppId] == appName) { - qDebug() << "Apps " << appId << " and " << otherAppId << " share name " << appName; - } -} -*/ -/* -Based on this, it seems necessary to group mimetypes by application name, rather than id. -A refactor is required -*/ -// m_applicationNames[appId] = appName; -// } - -/* -Apparently compilers these days literally cannot tell when a variable is not used or something, when compiling with -Wall -Werror on -What the fuck - - const QMimeType octetStream = - m_mimeDb.mimeTypeForName("application/octet-stream"); -*/ for (const QString &readMimeName : mimetypes) { // Resolve aliases etc - const QMimeType mimetype = - m_mimeDb.mimeTypeForName(readMimeName.trimmed()); + const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); if (!mimetype.isValid()) { // TODO This happens a TON. Why? //qDebug() << "In file " << appName << " mimetype " << readMimeName << " is invalid. Ignoring..."; @@ -427,49 +278,30 @@ What the fuck m_childMimeTypes.insert(parent, mimetypeName); } -/* -use m_apps instead - if (m_supportedMimetypes.contains(appName, mimetypeName)) { - // TODO check to make sure the appFile's are distinct (in case the user copied to .local/share/applications to override it for example) and print the appFiles of both conflicting definitions - continue; - } -*/ - if (mimetypeName.count('/') != 1) { - qDebug() << "Warning: encountered mimetype " << mimetypeName << " without exactly 1 '/' character in " << appFile << " Unsure what to do, skipping..."; + qDebug() << "Warning: encountered mimetype " << mimetypeName + << " without exactly 1 '/' character in " << appFile + << " Unsure what to do, skipping..."; continue; } -/* -TODO use type, I think this can still be deleted though. It needs to go elsewhere - const QString type = parts[0].trimmed(); -*/ // Indexing in Qt creates a default element if one doesn't exist, so we don't need to explicitely check if m_apps[appName] exists // If we've already got an association for this app from a different desktop file, don't overwrite it because we read highest-priority .desktops first if (m_apps[appName].contains(mimetypeName)) { // Annoyingly, some apps like KDE mobile apps add associations for *the same exact file type* through two different aliases, so this gets spammed a lot. - qDebug() << "Info: " << appName << " already handles " << mimetypeName << " with " << m_apps[appName][mimetypeName] << " so " << appFile << "will be ignored"; + qDebug() << "Debug: " << appName << " already handles " << mimetypeName << " with " + << m_apps[appName][mimetypeName] << " so " << appFile << "will be ignored"; continue; } m_apps[appName][mimetypeName] = appFile; -/* -Info is contained in m_apps, so use it elsewhere instead of this and remove this -*/ - //m_supportedMimetypes.insert(appName, mimetypeName); } } void SelectDefaultApplication::setDefault(const QString &appName, const QSet &mimetypes, - const QSet &unselectedMimetypes) + const QSet &unselectedMimetypes) { -/* -TODO we will need to associate both a mimetype and an appName (which will really be an appName then and not appId) to index a desktopFile, but for now appName is just the desktopFile - - QString desktopFile = m_desktopFileNames.value(appName); -*/ - const QString filePath = QDir(QStandardPaths::writableLocation( - QStandardPaths::ConfigLocation)) - .absoluteFilePath("mimeapps.list"); + const QString filePath = + QDir(QStandardPaths::writableLocation(QStandardPaths::ConfigLocation)).absoluteFilePath("mimeapps.list"); QFile file(filePath); // Read in existing mimeapps.list, skipping the lines for the mimetypes we're @@ -486,8 +318,7 @@ TODO we will need to associate both a mimetype and an appName (which will really } if (line.startsWith('[')) { - inCorrectGroup = - (line == "[Default Applications]"); + inCorrectGroup = (line == "[Default Applications]"); if (!inCorrectGroup) { existingContent.append(line); } @@ -504,24 +335,12 @@ TODO we will need to associate both a mimetype and an appName (which will really continue; } -/* -Doesn't appear to validate that it is a mimetype, which isn't good practice I think -Nevermind behaves correctly -*/ - const QString mimetype = - m_mimeDb.mimeTypeForName(line.split('=') - .first() - .trimmed()) - .name(); - if (!mimetypes.contains(mimetype) && - !unselectedMimetypes.contains(mimetype)) { + const QString mimetype = m_mimeDb.mimeTypeForName(line.split('=').first().trimmed()).name(); + if (!mimetypes.contains(mimetype) && !unselectedMimetypes.contains(mimetype)) { existingAssociations.append(line); } -/* -I'm pretty sure if unselectedMimetypes contains mimetype but .second().trimmed()).name() isn't equal to desktopFile then we should also add it to existingAssociations -Later...: Yeah I tested it and this is a bug -*/ + // Ensure that if a mimetype is unselected but set as default for a different application, we don't remove its entry from configuration if (unselectedMimetypes.contains(mimetype)) { const QString handlingAppFile = line.split('=')[1]; const QString appFile = m_apps[appName][mimetype]; @@ -533,16 +352,12 @@ Later...: Yeah I tested it and this is a bug file.close(); } else { - qDebug() << "Unable to open file for reading"; -/* -If we can't open the file for reading, we better stop before opening for writing and deleting it -Unless we check explicitely and the file isn't there at all -*/ + qWarning() << "Unable to open file for reading" << file.errorString(); + // TODO If we can't open the file for reading, we better stop before opening for writing and deleting it } if (!file.open(QIODevice::WriteOnly)) { - QMessageBox::warning(this, tr("Failed to store settings"), - file.errorString()); + QMessageBox::warning(this, tr("Failed to store settings"), file.errorString()); return; } @@ -555,17 +370,23 @@ Unless we check explicitely and the file isn't there at all } for (const QString &mimetype : mimetypes) { - file.write(QString(mimetype + '=' + - m_apps[appName][mimetype] + '\n') - .toUtf8()); + file.write(QString(mimetype + '=' + m_apps[appName][mimetype] + '\n').toUtf8()); } } -void SelectDefaultApplication::populateApplicationList(const QString &filter) { +void SelectDefaultApplication::populateApplicationList(const QString &filter) +{ + // Clear the list in case we are updating it (i.e. performing a search) m_applicationList->clear(); + // Filter entries based on the filter string QStringList applications = m_apps.keys().filter(filter); + + // Sort the remaining applications + // TODO If this is a performance issue, we can keep a seperate array pre-sorted std::sort(applications.begin(), applications.end()); + + // Add each application to the left panel for (const QString &appName : applications) { QListWidgetItem *app = new QListWidgetItem(appName); app->setIcon(QIcon::fromTheme(m_applicationIcons[appName])); @@ -582,8 +403,7 @@ void SelectDefaultApplication::loadIcons(const QString &path) } // TODO: avoid hardcoding QStringList imageTypes({ "*.svg", "*.svgz", "*.png", "*.xpm" }); - QDirIterator iter(path, imageTypes, QDir::Files, - QDirIterator::Subdirectories); + QDirIterator iter(path, imageTypes, QDir::Files, QDirIterator::Subdirectories); while (iter.hasNext()) { iter.next(); From 9e4b869edc7cbdc07fdf60d31be866a39053215a Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 05:12:58 -0600 Subject: [PATCH 19/38] Get ready to add more functionality in header --- selectdefaultapplication.h | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 7f87589..605b7de 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -28,25 +28,29 @@ private slots: void loadIcons(const QString &path); void populateApplicationList(const QString &filter); void addToMimetypeList(const QString &mimetypeName, const bool selected); - - // Hashtable of application names to mimetypes - // Used because it is difficult to extract the information from m_apps' multiple different hashtables - // JK its not used - //QMultiHash m_supportedMimetypes; - // Hashtable with keys as parent mime types and values as all children of that mimetype which are encountered - QMultiHash m_childMimeTypes; + QMultiHash getDefaultDesktopEntries(); // Hashtable of application names to hashtables of mimetypes to .desktop file entries QHash > m_apps; // Hashtable of application names to icons QHash m_applicationIcons; - //QHash m_applicationNames; - //QHash m_desktopFileNames; + // Multi-hashtable with keys as parent mime types and values as all children of that mimetype which are encountered + QMultiHash m_childMimeTypes; + + // Set containing all the mimegroups we saw + QSet m_mimegroups; + // Multi-hashtable with keys as application names and values as mimetypes + QMultiHash m_defaultApps; + // Multi-hashtable with keys as .desktop files and values as mimetypes, read from mimeapps.list + // Note this is opposite how they are actually stored. It is done this way so that we can read mimeapps.list before + // parsing anything else and then as we loop over all .desktop files, fill up the associations between programs and + // mimetypes. Remains constant after startup + QMultiHash m_defaultDesktopEntries; QHash m_appIdToDesktopFile; - QHash - m_mimeTypeIcons; // for preloading icons, because that's (a bit) slooow + // for preloading icons, because that's (a bit) slooow + QHash m_mimeTypeIcons; QHash m_iconPaths; QListWidget *m_applicationList; From eb01bf1ea658f90afebff55fceef48f93317e52d Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 05:30:51 -0600 Subject: [PATCH 20/38] Add mimegroups to mimegroups set --- selectdefaultapplication.cpp | 3 +++ selectdefaultapplication.h | 1 + 2 files changed, 4 insertions(+) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 57f622f..c808d85 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -284,6 +284,9 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) << " Unsure what to do, skipping..."; continue; } + // Now that we've checked this, we can get the mimegroup and add it to the global list + const QString mimegroup = mimetypeName.section('/', 0, 0); + m_mimegroups.insert(mimegroup); // Indexing in Qt creates a default element if one doesn't exist, so we don't need to explicitely check if m_apps[appName] exists // If we've already got an association for this app from a different desktop file, don't overwrite it because we read highest-priority .desktops first diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 605b7de..3a598cd 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -4,6 +4,7 @@ #include #include #include +#include class QFileInfo; class QTreeWidget; From 13b96af23f61fe54a61e3d5c410ba910f2f2c2c5 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 06:41:37 -0600 Subject: [PATCH 21/38] Working towards a much better gGUI --- selectdefaultapplication.cpp | 109 ++++++++++++++++++++++++++++++----- selectdefaultapplication.h | 16 ++++- 2 files changed, 107 insertions(+), 18 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index c808d85..5702bbf 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -14,6 +14,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(parent) { + readCurrentDefaultMimetypes(); for (const QString &dirPath : QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { qDebug() << "Loading applications from" << dirPath; QDir applicationsDir(dirPath); @@ -94,13 +95,36 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa m_mimetypeList = new QListWidget; m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); - QGridLayout *rightLayout = new QGridLayout; - rightLayout->addWidget(m_mimetypeList); - rightLayout->addWidget(m_setDefaultButton); + m_rightBanner = new QLabel(""); + m_middleBanner = new QLabel("Select an application to see its defaults."); + + m_currentDefaultApps = new QListWidget; + m_currentDefaultApps->setSelectionMode(QAbstractItemView::NoSelection); + + m_searchBox = new QLineEdit; + m_searchBox->setPlaceholderText("Search for Application"); + + m_groupChooser = new QToolButton; + m_groupChooser->setPopupMode(QToolButton::ToolButtonPopupMode::InstantPopup); + + QHBoxLayout *filterHolder = new QHBoxLayout; + + QVBoxLayout *leftLayout = new QVBoxLayout; + leftLayout->addWidget(m_applicationList); + + QVBoxLayout *middleLayout = new QVBoxLayout; + middleLayout->addWidget(m_middleBanner); + middleLayout->addWidget(m_mimetypeList); + middleLayout->addWidget(m_setDefaultButton); + + QVBoxLayout *rightLayout = new QVBoxLayout; + rightLayout->addWidget(m_rightBanner); + rightLayout->addWidget(m_currentDefaultApps); QHBoxLayout *mainLayout = new QHBoxLayout; setLayout(mainLayout); - mainLayout->addWidget(m_applicationList); + mainLayout->addLayout(leftLayout); + mainLayout->addLayout(middleLayout); mainLayout->addLayout(rightLayout); connect(m_applicationList, &QListWidget::itemSelectionChanged, this, @@ -128,16 +152,22 @@ void SelectDefaultApplication::onApplicationSelected() } const QListWidgetItem *item = selectedItems.first(); + const QString appName = item->data(0).toString(); + + // Set banners and right widget + m_middleBanner->setText(appName + " can open these filetypes:"); + m_rightBanner->setText("Default mimetypes " + appName + " will open:"); + m_currentDefaultApps->clear(); + for (QString &mimetype : m_defaultApps.values(appName)) { + addToMimetypeList(m_currentDefaultApps, mimetype, false); + } - const QString application = item->data(0).toString(); - - const QStringList officiallySupported = m_apps.value(application).keys(); + const QStringList officiallySupported = m_apps.value(appName).keys(); // TODO allow the user to check different mimetype groups to see only applications that affect those groups, and here remove mimetypes not in that group //if (!supportedMime.startsWith(mimetypeGroup)) { continue; } - // E. g. kwrite and kate only indicate support for "text/plain", but - // they're nice for things like c source files. + // E. g. kwrite and kate only indicate support for "text/plain", but they're nice for things like C source files. QSet impliedSupported; for (const QString &mimetype : officiallySupported) { for (const QString &child : m_childMimeTypes.values(mimetype)) { @@ -146,18 +176,20 @@ void SelectDefaultApplication::onApplicationSelected() } for (const QString &mimetype : officiallySupported) { - addToMimetypeList(mimetype, true); + addToMimetypeList(m_mimetypeList, mimetype, true); } for (const QString &mimetype : impliedSupported) { - addToMimetypeList(mimetype, false); + addToMimetypeList(m_mimetypeList, mimetype, false); } m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } -void SelectDefaultApplication::addToMimetypeList(const QString &mimetypeDirtyName, const bool selected) +void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimetypeDirtyName, const bool selected) { + // I didn't believe this was necessary, I tested, it is necessary. application/x-pkcs12 showed up here but is converted to application/pkcs12 const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeDirtyName); const QString mimeName = mimetype.name(); + QString name = mimetype.filterString().trimmed(); if (name.isEmpty()) { name = mimetype.comment().trimmed(); @@ -170,7 +202,7 @@ void SelectDefaultApplication::addToMimetypeList(const QString &mimetypeDirtyNam QListWidgetItem *item = new QListWidgetItem(name); item->setData(Qt::UserRole, mimeName); item->setIcon(m_mimeTypeIcons[mimetypeDirtyName]); - m_mimetypeList->addItem(item); + list->addItem(item); item->setSelected(selected); } @@ -257,6 +289,13 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) return; } + // Note that this program is the one that can edit some files from the defaults, if it is + if (m_defaultDesktopEntries.contains(appFile)) { + for (QString &mimetype : m_defaultDesktopEntries.values(appFile)) { + m_defaultApps.insert(appName, mimetype); + } + } + for (const QString &readMimeName : mimetypes) { // Resolve aliases etc const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); @@ -307,8 +346,7 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet existingContent; QList existingAssociations; if (file.open(QIODevice::ReadOnly)) { @@ -377,6 +415,47 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet #include #include +#include +#include +#include #include class QFileInfo; @@ -28,8 +31,8 @@ private slots: const QSet &unselectedMimetypes); void loadIcons(const QString &path); void populateApplicationList(const QString &filter); - void addToMimetypeList(const QString &mimetypeName, const bool selected); - QMultiHash getDefaultDesktopEntries(); + void addToMimetypeList(QListWidget *list, const QString &mimetypeName, const bool selected); + void readCurrentDefaultMimetypes(); // Hashtable of application names to hashtables of mimetypes to .desktop file entries QHash > m_apps; @@ -54,10 +57,17 @@ private slots: QHash m_mimeTypeIcons; QHash m_iconPaths; - QListWidget *m_applicationList; QMimeDatabase m_mimeDb; + + // UI elements + QListWidget *m_applicationList; QListWidget *m_mimetypeList; + QListWidget *m_currentDefaultApps; QPushButton *m_setDefaultButton; + QLineEdit *m_searchBox; + QToolButton *m_groupChooser; + QLabel *m_middleBanner; + QLabel *m_rightBanner; }; #endif // WIDGET_H From 3c02a144df8e30a176abfa3060960d7e0eecd41c Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 07:09:56 -0600 Subject: [PATCH 22/38] SORTING!!! --- selectdefaultapplication.cpp | 22 +++++++++++++--------- selectdefaultapplication.h | 6 +++--- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 5702bbf..79e6892 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -86,7 +86,6 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa m_applicationList = new QListWidget; m_applicationList->setSelectionMode(QAbstractItemView::SingleSelection); - // TODO allow user to search for applications populateApplicationList(""); m_setDefaultButton = new QPushButton(tr("Set as default application for these file types")); @@ -96,20 +95,23 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); m_rightBanner = new QLabel(""); - m_middleBanner = new QLabel("Select an application to see its defaults."); + m_middleBanner = new QLabel(tr("Select an application to see its defaults.")); m_currentDefaultApps = new QListWidget; m_currentDefaultApps->setSelectionMode(QAbstractItemView::NoSelection); m_searchBox = new QLineEdit; - m_searchBox->setPlaceholderText("Search for Application"); + m_searchBox->setPlaceholderText(tr("Search for Application")); - m_groupChooser = new QToolButton; - m_groupChooser->setPopupMode(QToolButton::ToolButtonPopupMode::InstantPopup); + m_groupChooser = new QPushButton; + m_groupChooser->setText(tr("Choose Group")); QHBoxLayout *filterHolder = new QHBoxLayout; + filterHolder->addWidget(m_searchBox); + filterHolder->addWidget(m_groupChooser); QVBoxLayout *leftLayout = new QVBoxLayout; + leftLayout->addLayout(filterHolder); leftLayout->addWidget(m_applicationList); QVBoxLayout *middleLayout = new QVBoxLayout; @@ -130,6 +132,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa connect(m_applicationList, &QListWidget::itemSelectionChanged, this, &SelectDefaultApplication::onApplicationSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); + connect(m_searchBox, &QLineEdit::textEdited, this, &SelectDefaultApplication::populateApplicationList); } SelectDefaultApplication::~SelectDefaultApplication() @@ -155,8 +158,8 @@ void SelectDefaultApplication::onApplicationSelected() const QString appName = item->data(0).toString(); // Set banners and right widget - m_middleBanner->setText(appName + " can open these filetypes:"); - m_rightBanner->setText("Default mimetypes " + appName + " will open:"); + m_middleBanner->setText(appName + tr(" can open these filetypes:")); + m_rightBanner->setText(tr("Configured mimetypes ") + appName + tr(" will open:")); m_currentDefaultApps->clear(); for (QString &mimetype : m_defaultApps.values(appName)) { addToMimetypeList(m_currentDefaultApps, mimetype, false); @@ -184,7 +187,8 @@ void SelectDefaultApplication::onApplicationSelected() m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } -void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimetypeDirtyName, const bool selected) +void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimetypeDirtyName, + const bool selected) { // I didn't believe this was necessary, I tested, it is necessary. application/x-pkcs12 showed up here but is converted to application/pkcs12 const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeDirtyName); @@ -462,7 +466,7 @@ void SelectDefaultApplication::populateApplicationList(const QString &filter) m_applicationList->clear(); // Filter entries based on the filter string - QStringList applications = m_apps.keys().filter(filter); + QStringList applications = m_apps.keys().filter(filter, Qt::CaseInsensitive); // Sort the remaining applications // TODO If this is a performance issue, we can keep a seperate array pre-sorted diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index a00bbfb..c9da0e1 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include #include @@ -24,13 +24,13 @@ class SelectDefaultApplication : public QWidget { private slots: void onApplicationSelected(); void onSetDefaultClicked(); + void populateApplicationList(const QString &filter); private: void loadDesktopFile(const QFileInfo &fileInfo); void setDefault(const QString &appName, const QSet &mimetypes, const QSet &unselectedMimetypes); void loadIcons(const QString &path); - void populateApplicationList(const QString &filter); void addToMimetypeList(QListWidget *list, const QString &mimetypeName, const bool selected); void readCurrentDefaultMimetypes(); @@ -65,7 +65,7 @@ private slots: QListWidget *m_currentDefaultApps; QPushButton *m_setDefaultButton; QLineEdit *m_searchBox; - QToolButton *m_groupChooser; + QPushButton *m_groupChooser; QLabel *m_middleBanner; QLabel *m_rightBanner; }; From 8673b4fd1206a723373bc0eeca5d67104512f238 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 07:35:18 -0600 Subject: [PATCH 23/38] Cleanup a bit --- selectdefaultapplication.cpp | 45 +++++++++++++++++++++++++----------- selectdefaultapplication.h | 5 +++- 2 files changed, 36 insertions(+), 14 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 79e6892..ceeff0e 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -84,22 +84,12 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa } } + // The rest of this constructor sets up the GUI + // Left section m_applicationList = new QListWidget; m_applicationList->setSelectionMode(QAbstractItemView::SingleSelection); populateApplicationList(""); - m_setDefaultButton = new QPushButton(tr("Set as default application for these file types")); - m_setDefaultButton->setEnabled(false); - - m_mimetypeList = new QListWidget; - m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); - - m_rightBanner = new QLabel(""); - m_middleBanner = new QLabel(tr("Select an application to see its defaults.")); - - m_currentDefaultApps = new QListWidget; - m_currentDefaultApps->setSelectionMode(QAbstractItemView::NoSelection); - m_searchBox = new QLineEdit; m_searchBox->setPlaceholderText(tr("Search for Application")); @@ -114,15 +104,39 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa leftLayout->addLayout(filterHolder); leftLayout->addWidget(m_applicationList); + // Middle section + m_middleBanner = new QLabel(tr("Select an application to see its defaults.")); + + m_mimetypeList = new QListWidget; + m_mimetypeList->setSelectionMode(QAbstractItemView::MultiSelection); + + m_setDefaultButton = new QPushButton(); + m_setDefaultButton->setText(tr("Set as default application for these file types")); + m_setDefaultButton->setEnabled(false); + QVBoxLayout *middleLayout = new QVBoxLayout; middleLayout->addWidget(m_middleBanner); middleLayout->addWidget(m_mimetypeList); middleLayout->addWidget(m_setDefaultButton); + // Right section + m_rightBanner = new QLabel(""); + + m_infoButton = new QToolButton(); + m_infoButton->setText("?"); + + QHBoxLayout *infoHolder = new QHBoxLayout; + infoHolder->addWidget(m_rightBanner); + infoHolder->addWidget(m_infoButton); + + m_currentDefaultApps = new QListWidget; + m_currentDefaultApps->setSelectionMode(QAbstractItemView::NoSelection); + QVBoxLayout *rightLayout = new QVBoxLayout; - rightLayout->addWidget(m_rightBanner); + rightLayout->addLayout(infoHolder); rightLayout->addWidget(m_currentDefaultApps); + // Main layout and connections QHBoxLayout *mainLayout = new QHBoxLayout; setLayout(mainLayout); mainLayout->addLayout(leftLayout); @@ -132,6 +146,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa connect(m_applicationList, &QListWidget::itemSelectionChanged, this, &SelectDefaultApplication::onApplicationSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); + connect(m_infoButton, &QToolButton::clicked, this, &SelectDefaultApplication::showHelp); connect(m_searchBox, &QLineEdit::textEdited, this, &SelectDefaultApplication::populateApplicationList); } @@ -502,3 +517,7 @@ void SelectDefaultApplication::loadIcons(const QString &path) m_iconPaths[name] = icon_file.filePath(); } } + +void SelectDefaultApplication::showHelp() { + qDebug() << "HELP"; +} \ No newline at end of file diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index c9da0e1..f6ec36e 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include @@ -25,6 +26,7 @@ private slots: void onApplicationSelected(); void onSetDefaultClicked(); void populateApplicationList(const QString &filter); + void showHelp(); private: void loadDesktopFile(const QFileInfo &fileInfo); @@ -63,9 +65,10 @@ private slots: QListWidget *m_applicationList; QListWidget *m_mimetypeList; QListWidget *m_currentDefaultApps; - QPushButton *m_setDefaultButton; QLineEdit *m_searchBox; QPushButton *m_groupChooser; + QPushButton *m_setDefaultButton; + QToolButton *m_infoButton; QLabel *m_middleBanner; QLabel *m_rightBanner; }; From 11bc8e7f1419ac520cfdab8d932c75a9e031dfae Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 07:53:30 -0600 Subject: [PATCH 24/38] Add help --- selectdefaultapplication.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index ceeff0e..d92ca84 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -147,7 +147,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa &SelectDefaultApplication::onApplicationSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); connect(m_infoButton, &QToolButton::clicked, this, &SelectDefaultApplication::showHelp); - connect(m_searchBox, &QLineEdit::textEdited, this, &SelectDefaultApplication::populateApplicationList); + connect(m_searchBox, &QLineEdit::textChanged, this, &SelectDefaultApplication::populateApplicationList); } SelectDefaultApplication::~SelectDefaultApplication() @@ -518,6 +518,20 @@ void SelectDefaultApplication::loadIcons(const QString &path) } } -void SelectDefaultApplication::showHelp() { - qDebug() << "HELP"; +void SelectDefaultApplication::showHelp() +{ + QMessageBox *dialog = new QMessageBox(this); + dialog->setText(tr( + "To use this program, select any applications on the left panel.\n" + "Then select or deselect any mimetypes in the center that you want this application to open. Most of the time, you can leave this at the defaults; it will choose all the mimetypes the application has explicit support for.\n" + "Finally, press at the bottom of the screen to make the highlighted mimetypes open with the selected application by default.\n" + "You can see your changes on the right. Any application that you have configured will report it there.\n" + "---------------------------------------------------------------------------------------\n" + "Explanation of how this works: A FreeDesktop has Desktop Entries (.desktop files) in several locations; /usr/share/applications/, /usr/local/share/applications/, and ~/.local/share/applications/ by default.\n" + "These desktop entries tell application launchers how to run programs, including the tool 'xdg-open' which is the standard tool to open files and URLs.\n" + "xdg-open reads Desktop Entries in an unpredictable order in order to determine what application to handle that file; it uses the `MimeTypes` key present in a Desktop Entry to determine this.\n" + "There is also a user configuration file, `~/.config/mimeapps.list`, which it reads first and gives higher precedence.\n" + "This program parses all the application files located on the system, as well as the `mimeapps.list`, to determine what programs exist and which are set as defaults.\n" + "Then, when you click to 'set as default for these filetypes', it reads `mimeapps.list`, and sets the keys you have highlighted to the new values.\n")); + dialog->exec(); } \ No newline at end of file From f073fe4130592babc895a8170824e1abf9f1d596 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 08:15:25 -0600 Subject: [PATCH 25/38] Close to mimegroup filtering --- selectdefaultapplication.cpp | 44 ++++++++++++++++++++++++++---------- selectdefaultapplication.h | 5 ++++ 2 files changed, 37 insertions(+), 12 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index d92ca84..e0d46bc 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -94,7 +94,16 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa m_searchBox->setPlaceholderText(tr("Search for Application")); m_groupChooser = new QPushButton; - m_groupChooser->setText(tr("Choose Group")); + m_groupChooser->setText(tr("All")); + + m_mimegroupMenu = new QMenu(m_groupChooser); + m_mimegroupMenu->addAction(tr("All")); + QStringList sorted_mimegroups = m_mimegroups.values(); + sorted_mimegroups.sort(); + for (const QString &mimegroup : sorted_mimegroups) { + m_mimegroupMenu->addAction(mimegroup); + } + m_groupChooser->setMenu(m_mimegroupMenu); QHBoxLayout *filterHolder = new QHBoxLayout; filterHolder->addWidget(m_searchBox); @@ -148,17 +157,18 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); connect(m_infoButton, &QToolButton::clicked, this, &SelectDefaultApplication::showHelp); connect(m_searchBox, &QLineEdit::textChanged, this, &SelectDefaultApplication::populateApplicationList); + connect(m_mimegroupMenu, &QMenu::triggered, this, &SelectDefaultApplication::constrictGroup); } SelectDefaultApplication::~SelectDefaultApplication() { } -/* -Populates the right side of the screen. Selects all the mimetypes that application can natively support -TODO distinguish between mimetypes the application currently is the default of, mimetypes the application natively supports, children of the application's supported types -Currently only distinguishes between the latter two -*/ +/** + * Populates the middle and right side of the screen. + * Selects all the mimetypes that application can natively support for the middle, and all currently selected for right + * Filters mimetypes based on if they start with m_filterMimegroup + */ void SelectDefaultApplication::onApplicationSelected() { m_setDefaultButton->setEnabled(false); @@ -194,19 +204,22 @@ void SelectDefaultApplication::onApplicationSelected() } for (const QString &mimetype : officiallySupported) { - addToMimetypeList(m_mimetypeList, mimetype, true); + if (mimetype.startsWith(m_filterMimegroup)) { + addToMimetypeList(m_mimetypeList, mimetype, true); + } } for (const QString &mimetype : impliedSupported) { - addToMimetypeList(m_mimetypeList, mimetype, false); + if (mimetype.startsWith(m_filterMimegroup)) { + addToMimetypeList(m_mimetypeList, mimetype, false); + } } m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); } -void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimetypeDirtyName, - const bool selected) +void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimeDirtyName, const bool selected) { // I didn't believe this was necessary, I tested, it is necessary. application/x-pkcs12 showed up here but is converted to application/pkcs12 - const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeDirtyName); + const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimeDirtyName); const QString mimeName = mimetype.name(); QString name = mimetype.filterString().trimmed(); @@ -220,7 +233,7 @@ void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QStrin } QListWidgetItem *item = new QListWidgetItem(name); item->setData(Qt::UserRole, mimeName); - item->setIcon(m_mimeTypeIcons[mimetypeDirtyName]); + item->setIcon(m_mimeTypeIcons[mimeDirtyName]); list->addItem(item); item->setSelected(selected); } @@ -518,6 +531,13 @@ void SelectDefaultApplication::loadIcons(const QString &path) } } +void SelectDefaultApplication::constrictGroup(QAction *action) +{ + m_filterMimegroup = (action->text() == tr("All")) ? "" : action->text(); + qDebug() << action->text() << "Compared with" << m_filterMimegroup; + m_searchBox->clear(); +} + void SelectDefaultApplication::showHelp() { QMessageBox *dialog = new QMessageBox(this); diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index f6ec36e..2cb22d0 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -9,6 +9,7 @@ #include #include #include +#include class QFileInfo; class QTreeWidget; @@ -27,6 +28,7 @@ private slots: void onSetDefaultClicked(); void populateApplicationList(const QString &filter); void showHelp(); + void constrictGroup(QAction *action); private: void loadDesktopFile(const QFileInfo &fileInfo); @@ -45,6 +47,8 @@ private slots: // Set containing all the mimegroups we saw QSet m_mimegroups; + // Global variable to match selected mimegroup on + QString m_filterMimegroup; // Multi-hashtable with keys as application names and values as mimetypes QMultiHash m_defaultApps; // Multi-hashtable with keys as .desktop files and values as mimetypes, read from mimeapps.list @@ -67,6 +71,7 @@ private slots: QListWidget *m_currentDefaultApps; QLineEdit *m_searchBox; QPushButton *m_groupChooser; + QMenu *m_mimegroupMenu; QPushButton *m_setDefaultButton; QToolButton *m_infoButton; QLabel *m_middleBanner; From 9d4408127ee2e3f0df201b289f16342d0cbbdf01 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 08:26:14 -0600 Subject: [PATCH 26/38] Very sad Qt6 solution --- selectdefaultapplication.cpp | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index e0d46bc..285de92 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -164,6 +164,10 @@ SelectDefaultApplication::~SelectDefaultApplication() { } +bool SelectDefaultApplication::stringMeetsMimegroupFilter(const QString &s) { + return s.startsWith(m_filterMimegroup); +} + /** * Populates the middle and right side of the screen. * Selects all the mimetypes that application can natively support for the middle, and all currently selected for right @@ -190,7 +194,8 @@ void SelectDefaultApplication::onApplicationSelected() addToMimetypeList(m_currentDefaultApps, mimetype, false); } - const QStringList officiallySupported = m_apps.value(appName).keys(); + QStringList officiallySupported = m_apps.value(appName).keys(); + officiallySupported.removeIf(stringMeetsMimegroupFilter); // TODO allow the user to check different mimetype groups to see only applications that affect those groups, and here remove mimetypes not in that group //if (!supportedMime.startsWith(mimetypeGroup)) { continue; } @@ -202,16 +207,13 @@ void SelectDefaultApplication::onApplicationSelected() impliedSupported.insert(child); } } + impliedSupported.removeIf(stringMeetsMimegroupFilter); for (const QString &mimetype : officiallySupported) { - if (mimetype.startsWith(m_filterMimegroup)) { - addToMimetypeList(m_mimetypeList, mimetype, true); - } + addToMimetypeList(m_mimetypeList, mimetype, true); } for (const QString &mimetype : impliedSupported) { - if (mimetype.startsWith(m_filterMimegroup)) { - addToMimetypeList(m_mimetypeList, mimetype, false); - } + addToMimetypeList(m_mimetypeList, mimetype, false); } m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); @@ -488,6 +490,7 @@ void SelectDefaultApplication::readCurrentDefaultMimetypes() } } +// Adds applications to the leftmost tab if they match the filter and the mimegroup restrictions void SelectDefaultApplication::populateApplicationList(const QString &filter) { // Clear the list in case we are updating it (i.e. performing a search) @@ -495,10 +498,10 @@ void SelectDefaultApplication::populateApplicationList(const QString &filter) // Filter entries based on the filter string QStringList applications = m_apps.keys().filter(filter, Qt::CaseInsensitive); + applications.removeIf(stringMeetsMimegroupFilter); // Sort the remaining applications - // TODO If this is a performance issue, we can keep a seperate array pre-sorted - std::sort(applications.begin(), applications.end()); + applications.sort(); // Add each application to the left panel for (const QString &appName : applications) { From 6b21d59baf3de184ac9f4ef2df6341a071b98942 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 08:48:47 -0600 Subject: [PATCH 27/38] Correct sorting based on actions --- selectdefaultapplication.cpp | 41 ++++++++++++++++++++++++------------ selectdefaultapplication.h | 1 + 2 files changed, 28 insertions(+), 14 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 285de92..dcc9a73 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -156,7 +156,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa &SelectDefaultApplication::onApplicationSelected); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); connect(m_infoButton, &QToolButton::clicked, this, &SelectDefaultApplication::showHelp); - connect(m_searchBox, &QLineEdit::textChanged, this, &SelectDefaultApplication::populateApplicationList); + connect(m_searchBox, &QLineEdit::textEdited, this, &SelectDefaultApplication::populateApplicationList); connect(m_mimegroupMenu, &QMenu::triggered, this, &SelectDefaultApplication::constrictGroup); } @@ -164,10 +164,6 @@ SelectDefaultApplication::~SelectDefaultApplication() { } -bool SelectDefaultApplication::stringMeetsMimegroupFilter(const QString &s) { - return s.startsWith(m_filterMimegroup); -} - /** * Populates the middle and right side of the screen. * Selects all the mimetypes that application can natively support for the middle, and all currently selected for right @@ -194,8 +190,7 @@ void SelectDefaultApplication::onApplicationSelected() addToMimetypeList(m_currentDefaultApps, mimetype, false); } - QStringList officiallySupported = m_apps.value(appName).keys(); - officiallySupported.removeIf(stringMeetsMimegroupFilter); + const QStringList officiallySupported = m_apps.value(appName).keys(); // TODO allow the user to check different mimetype groups to see only applications that affect those groups, and here remove mimetypes not in that group //if (!supportedMime.startsWith(mimetypeGroup)) { continue; } @@ -207,13 +202,16 @@ void SelectDefaultApplication::onApplicationSelected() impliedSupported.insert(child); } } - impliedSupported.removeIf(stringMeetsMimegroupFilter); for (const QString &mimetype : officiallySupported) { - addToMimetypeList(m_mimetypeList, mimetype, true); + if (mimetype.startsWith(m_filterMimegroup)) { + addToMimetypeList(m_mimetypeList, mimetype, true); + } } for (const QString &mimetype : impliedSupported) { - addToMimetypeList(m_mimetypeList, mimetype, false); + if (mimetype.startsWith(m_filterMimegroup)) { + addToMimetypeList(m_mimetypeList, mimetype, false); + } } m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); @@ -490,7 +488,6 @@ void SelectDefaultApplication::readCurrentDefaultMimetypes() } } -// Adds applications to the leftmost tab if they match the filter and the mimegroup restrictions void SelectDefaultApplication::populateApplicationList(const QString &filter) { // Clear the list in case we are updating it (i.e. performing a search) @@ -498,7 +495,12 @@ void SelectDefaultApplication::populateApplicationList(const QString &filter) // Filter entries based on the filter string QStringList applications = m_apps.keys().filter(filter, Qt::CaseInsensitive); - applications.removeIf(stringMeetsMimegroupFilter); + // Iterate over the array, removing elements who have no desktop entries which can handle the correct mimetype + for (QStringList::size_type i = applications.size(); i--;) { + if (!applicationHasAnyCorrectMimetype(applications[i])) { + applications.removeAt(i); + } + } // Sort the remaining applications applications.sort(); @@ -536,9 +538,11 @@ void SelectDefaultApplication::loadIcons(const QString &path) void SelectDefaultApplication::constrictGroup(QAction *action) { + m_groupChooser->setText(action->text()); m_filterMimegroup = (action->text() == tr("All")) ? "" : action->text(); - qDebug() << action->text() << "Compared with" << m_filterMimegroup; m_searchBox->clear(); + populateApplicationList(""); + onApplicationSelected(); } void SelectDefaultApplication::showHelp() @@ -557,4 +561,13 @@ void SelectDefaultApplication::showHelp() "This program parses all the application files located on the system, as well as the `mimeapps.list`, to determine what programs exist and which are set as defaults.\n" "Then, when you click to 'set as default for these filetypes', it reads `mimeapps.list`, and sets the keys you have highlighted to the new values.\n")); dialog->exec(); -} \ No newline at end of file +} + +bool SelectDefaultApplication::applicationHasAnyCorrectMimetype(const QString &appName) { + for (QString appFile : m_apps[appName].keys()) { + if (appFile.startsWith(m_filterMimegroup)) { + return true; + } + } + return false; +} diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 2cb22d0..d808210 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -37,6 +37,7 @@ private slots: void loadIcons(const QString &path); void addToMimetypeList(QListWidget *list, const QString &mimetypeName, const bool selected); void readCurrentDefaultMimetypes(); + bool applicationHasAnyCorrectMimetype(const QString &appName); // Hashtable of application names to hashtables of mimetypes to .desktop file entries QHash > m_apps; From bc9c2b8c7efc2c80ad36ac876d8c28b99ae80ef7 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 09:07:28 -0600 Subject: [PATCH 28/38] Almost done making it responsive but there is a bug --- selectdefaultapplication.cpp | 17 +++++++++++++---- selectdefaultapplication.h | 4 ++-- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index dcc9a73..86b8107 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -186,7 +186,7 @@ void SelectDefaultApplication::onApplicationSelected() m_middleBanner->setText(appName + tr(" can open these filetypes:")); m_rightBanner->setText(tr("Configured mimetypes ") + appName + tr(" will open:")); m_currentDefaultApps->clear(); - for (QString &mimetype : m_defaultApps.values(appName)) { + for (QString &mimetype : m_defaultApps.keys(appName)) { addToMimetypeList(m_currentDefaultApps, mimetype, false); } @@ -324,7 +324,7 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) // Note that this program is the one that can edit some files from the defaults, if it is if (m_defaultDesktopEntries.contains(appFile)) { for (QString &mimetype : m_defaultDesktopEntries.values(appFile)) { - m_defaultApps.insert(appName, mimetype); + m_defaultApps[mimetype] = appName; } } @@ -429,6 +429,7 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSetsetEnabled(false); } void SelectDefaultApplication::readCurrentDefaultMimetypes() @@ -563,7 +571,8 @@ void SelectDefaultApplication::showHelp() dialog->exec(); } -bool SelectDefaultApplication::applicationHasAnyCorrectMimetype(const QString &appName) { +bool SelectDefaultApplication::applicationHasAnyCorrectMimetype(const QString &appName) +{ for (QString appFile : m_apps[appName].keys()) { if (appFile.startsWith(m_filterMimegroup)) { return true; diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index d808210..983c4c5 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -50,8 +50,8 @@ private slots: QSet m_mimegroups; // Global variable to match selected mimegroup on QString m_filterMimegroup; - // Multi-hashtable with keys as application names and values as mimetypes - QMultiHash m_defaultApps; + // Multi-hashtable with keys as mimetypes and values as application names + QHash m_defaultApps; // Multi-hashtable with keys as .desktop files and values as mimetypes, read from mimeapps.list // Note this is opposite how they are actually stored. It is done this way so that we can read mimeapps.list before // parsing anything else and then as we loop over all .desktop files, fill up the associations between programs and From fb71c894a650e42382206d5fede2edb96c90a8a3 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 09:53:13 -0600 Subject: [PATCH 29/38] UI real nice --- selectdefaultapplication.cpp | 35 ++++++++++++++++++++++------------- selectdefaultapplication.h | 2 ++ 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 86b8107..4438c23 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -154,6 +154,8 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa connect(m_applicationList, &QListWidget::itemSelectionChanged, this, &SelectDefaultApplication::onApplicationSelected); + connect(m_mimetypeList, &QListWidget::itemActivated, this, + &SelectDefaultApplication::enableSetDefaultButton); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); connect(m_infoButton, &QToolButton::clicked, this, &SelectDefaultApplication::showHelp); connect(m_searchBox, &QLineEdit::textEdited, this, &SelectDefaultApplication::populateApplicationList); @@ -165,11 +167,16 @@ SelectDefaultApplication::~SelectDefaultApplication() } /** - * Populates the middle and right side of the screen. - * Selects all the mimetypes that application can natively support for the middle, and all currently selected for right - * Filters mimetypes based on if they start with m_filterMimegroup + * Populates the middle and right side of the screen. + * Selects all the mimetypes that application can natively support for the middle, and all currently selected for right + * Filters mimetypes based on if they start with m_filterMimegroup + * Extra function is needed for Qt slots, which is in turn needed to stop the screen from flashing, which is unfortunate */ void SelectDefaultApplication::onApplicationSelected() +{ + onApplicationSelectedLogic(true); +} +void SelectDefaultApplication::onApplicationSelectedLogic(bool allowEnabled) { m_setDefaultButton->setEnabled(false); m_mimetypeList->clear(); @@ -192,9 +199,6 @@ void SelectDefaultApplication::onApplicationSelected() const QStringList officiallySupported = m_apps.value(appName).keys(); - // TODO allow the user to check different mimetype groups to see only applications that affect those groups, and here remove mimetypes not in that group - //if (!supportedMime.startsWith(mimetypeGroup)) { continue; } - // E. g. kwrite and kate only indicate support for "text/plain", but they're nice for things like C source files. QSet impliedSupported; for (const QString &mimetype : officiallySupported) { @@ -214,7 +218,7 @@ void SelectDefaultApplication::onApplicationSelected() } } - m_setDefaultButton->setEnabled(m_mimetypeList->count() > 0); + m_setDefaultButton->setEnabled(allowEnabled && m_mimetypeList->count() > 0); } void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimeDirtyName, const bool selected) { @@ -411,13 +415,15 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSetsetEnabled(false); + onApplicationSelectedLogic(false); } void SelectDefaultApplication::readCurrentDefaultMimetypes() @@ -553,6 +558,10 @@ void SelectDefaultApplication::constrictGroup(QAction *action) onApplicationSelected(); } +void SelectDefaultApplication::enableSetDefaultButton() { + m_setDefaultButton->setEnabled(true); +} + void SelectDefaultApplication::showHelp() { QMessageBox *dialog = new QMessageBox(this); diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 983c4c5..8d0ca02 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -29,6 +29,7 @@ private slots: void populateApplicationList(const QString &filter); void showHelp(); void constrictGroup(QAction *action); + void enableSetDefaultButton(); private: void loadDesktopFile(const QFileInfo &fileInfo); @@ -38,6 +39,7 @@ private slots: void addToMimetypeList(QListWidget *list, const QString &mimetypeName, const bool selected); void readCurrentDefaultMimetypes(); bool applicationHasAnyCorrectMimetype(const QString &appName); + void onApplicationSelectedLogic(bool allowEnable); // Hashtable of application names to hashtables of mimetypes to .desktop file entries QHash > m_apps; From 7fc2d678cb4f3180d2dcf0a6878fffa7569d5862 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 09:58:34 -0600 Subject: [PATCH 30/38] Fix a bad bugg where duplicate entries can get put into mimeapps.list --- selectdefaultapplication.cpp | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 4438c23..c632449 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -197,17 +197,20 @@ void SelectDefaultApplication::onApplicationSelectedLogic(bool allowEnabled) addToMimetypeList(m_currentDefaultApps, mimetype, false); } - const QStringList officiallySupported = m_apps.value(appName).keys(); + const QHash &officiallySupported = m_apps.value(appName); // E. g. kwrite and kate only indicate support for "text/plain", but they're nice for things like C source files. QSet impliedSupported; for (const QString &mimetype : officiallySupported) { for (const QString &child : m_childMimeTypes.values(mimetype)) { - impliedSupported.insert(child); + // Ensure that the officially supported keys don't contain this value + if (!officiallySupported.contains(child)) { + impliedSupported.insert(child); + } } } - for (const QString &mimetype : officiallySupported) { + for (const QString &mimetype : officiallySupported.keys()) { if (mimetype.startsWith(m_filterMimegroup)) { addToMimetypeList(m_mimetypeList, mimetype, true); } @@ -417,8 +420,7 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet Date: Thu, 30 Dec 2021 10:13:54 -0600 Subject: [PATCH 31/38] Command line options, set version number to 2.0, and added Verbose flag --- main.cpp | 20 +++++++++++++++++++- selectdefaultapplication.cpp | 32 ++++++++++++++++++++------------ selectdefaultapplication.h | 4 ++-- 3 files changed, 41 insertions(+), 15 deletions(-) diff --git a/main.cpp b/main.cpp index 4cd9adc..5495440 100644 --- a/main.cpp +++ b/main.cpp @@ -1,10 +1,28 @@ #include "selectdefaultapplication.h" #include +#include int main(int argc, char *argv[]) { + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); + QApplication a(argc, argv); - SelectDefaultApplication w; + a.setApplicationVersion("2.0"); + a.setApplicationDisplayName("Select Default Application"); + + QCommandLineParser parser; + parser.setApplicationDescription( + "A very simple application that lets you define default applications on Linux in a sane way."); + parser.addHelpOption(); + parser.addVersionOption(); + QCommandLineOption verbose(QStringList() << "V" + << "verbose", + "Print verbose information about how the desktop files are parsed"); + + parser.addOption(verbose); + parser.process(a); + + SelectDefaultApplication w(nullptr, parser.isSet(verbose)); w.show(); return a.exec(); diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index c632449..9759633 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -12,7 +12,8 @@ #include #include -SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(parent) +SelectDefaultApplication::SelectDefaultApplication(QWidget *parent, bool isVerbose) + : QWidget(parent), isVerbose(isVerbose) { readCurrentDefaultMimetypes(); for (const QString &dirPath : QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation)) { @@ -154,8 +155,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent) : QWidget(pa connect(m_applicationList, &QListWidget::itemSelectionChanged, this, &SelectDefaultApplication::onApplicationSelected); - connect(m_mimetypeList, &QListWidget::itemActivated, this, - &SelectDefaultApplication::enableSetDefaultButton); + connect(m_mimetypeList, &QListWidget::itemActivated, this, &SelectDefaultApplication::enableSetDefaultButton); connect(m_setDefaultButton, &QPushButton::clicked, this, &SelectDefaultApplication::onSetDefaultClicked); connect(m_infoButton, &QToolButton::clicked, this, &SelectDefaultApplication::showHelp); connect(m_searchBox, &QLineEdit::textEdited, this, &SelectDefaultApplication::populateApplicationList); @@ -339,8 +339,11 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) // Resolve aliases etc const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); if (!mimetype.isValid()) { - // TODO This happens a TON. Why? - //qDebug() << "In file " << appName << " mimetype " << readMimeName << " is invalid. Ignoring..."; + if (isVerbose) { + // TODO This happens a TON. Why? + qDebug() << "In file " << appName << " mimetype " << readMimeName + << " is invalid. Ignoring..."; + } continue; } const QString mimetypeName = mimetype.name(); @@ -357,9 +360,9 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) } if (mimetypeName.count('/') != 1) { - qDebug() << "Warning: encountered mimetype " << mimetypeName - << " without exactly 1 '/' character in " << appFile - << " Unsure what to do, skipping..."; + qWarning() << "Warning: encountered mimetype " << mimetypeName + << " without exactly 1 '/' character in " << appFile + << " Unsure what to do, skipping..."; continue; } // Now that we've checked this, we can get the mimegroup and add it to the global list @@ -370,8 +373,10 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) // If we've already got an association for this app from a different desktop file, don't overwrite it because we read highest-priority .desktops first if (m_apps[appName].contains(mimetypeName)) { // Annoyingly, some apps like KDE mobile apps add associations for *the same exact file type* through two different aliases, so this gets spammed a lot. - qDebug() << "Debug: " << appName << " already handles " << mimetypeName << " with " - << m_apps[appName][mimetypeName] << " so " << appFile << "will be ignored"; + if (isVerbose) { + qDebug() << "Debug: " << appName << " already handles " << mimetypeName << " with " + << m_apps[appName][mimetypeName] << " so " << appFile << "will be ignored"; + } continue; } m_apps[appName][mimetypeName] = appFile; @@ -454,6 +459,9 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSetsetEnabled(true); } diff --git a/selectdefaultapplication.h b/selectdefaultapplication.h index 8d0ca02..5aeca2c 100644 --- a/selectdefaultapplication.h +++ b/selectdefaultapplication.h @@ -20,7 +20,7 @@ class SelectDefaultApplication : public QWidget { Q_OBJECT public: - SelectDefaultApplication(QWidget *parent = nullptr); + SelectDefaultApplication(QWidget *parent, bool isVerbose); ~SelectDefaultApplication(); private slots: @@ -60,7 +60,7 @@ private slots: // mimetypes. Remains constant after startup QMultiHash m_defaultDesktopEntries; - QHash m_appIdToDesktopFile; + bool isVerbose; // for preloading icons, because that's (a bit) slooow QHash m_mimeTypeIcons; From b888f3765b56adc3553473af1ca81c7853ef52bc Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 10:19:41 -0600 Subject: [PATCH 32/38] format code --- selectdefaultapplication.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 9759633..814ecd8 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -460,7 +460,8 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet Date: Thu, 30 Dec 2021 10:26:47 -0600 Subject: [PATCH 33/38] Fix up help a little bit --- selectdefaultapplication.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 814ecd8..c88ebff 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -584,9 +584,9 @@ void SelectDefaultApplication::showHelp() "---------------------------------------------------------------------------------------\n" "Explanation of how this works: A FreeDesktop has Desktop Entries (.desktop files) in several locations; /usr/share/applications/, /usr/local/share/applications/, and ~/.local/share/applications/ by default.\n" "These desktop entries tell application launchers how to run programs, including the tool 'xdg-open' which is the standard tool to open files and URLs.\n" - "xdg-open reads Desktop Entries in an unpredictable order in order to determine what application to handle that file; it uses the `MimeTypes` key present in a Desktop Entry to determine this.\n" + "xdg-open reads Desktop Entries in an unpredictable order in order to determine what application to handle that file; it uses the `MimeType` key present in a Desktop Entry to determine this.\n" "There is also a user configuration file, `~/.config/mimeapps.list`, which it reads first and gives higher precedence.\n" - "This program parses all the application files located on the system, as well as the `mimeapps.list`, to determine what programs exist and which are set as defaults.\n" + "This program parses all the Desktop Entries on the system, as well as the `mimeapps.list`, to determine what programs exist and which are set as defaults.\n" "Then, when you click to 'set as default for these filetypes', it reads `mimeapps.list`, and sets the keys you have highlighted to the new values.\n")); dialog->exec(); } From 617800eb2cb6b0a358ee7300aae914ea2dd6b1ba Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 18:55:27 -0600 Subject: [PATCH 34/38] Get started fixing x-scheme-handler issues --- selectdefaultapplication.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index c88ebff..96fc10f 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -338,15 +338,16 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) for (const QString &readMimeName : mimetypes) { // Resolve aliases etc const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); - if (!mimetype.isValid()) { + if (!mimetype.isValid() && !readMimeName.startsWith("x-scheme-handler/")) { if (isVerbose) { // TODO This happens a TON. Why? qDebug() << "In file " << appName << " mimetype " << readMimeName - << " is invalid. Ignoring..."; + << " is invalid. Ignoring..." << mimetype.name(); } continue; } - const QString mimetypeName = mimetype.name(); + const QString mimetypeName = (mimetype.name() == "") ? readMimeName.trimmed() : mimetype.name(); +qDebug() << readMimeName << "Corresponds to" << mimetypeName; // Create a database of mimetypes this application is a child of // So applications that can edit parent mimetypes can also have associations formed to their child mimetypes From fc413ff0412fe711bf8108673d8608db999e2cfa Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 19:42:47 -0600 Subject: [PATCH 35/38] Allow parsing of x-scheme-handler and remove useless re-validation of mimetypes --- selectdefaultapplication.cpp | 48 +++++++++++++++++------------------- 1 file changed, 22 insertions(+), 26 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 96fc10f..81b9a17 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -223,24 +223,12 @@ void SelectDefaultApplication::onApplicationSelectedLogic(bool allowEnabled) m_setDefaultButton->setEnabled(allowEnabled && m_mimetypeList->count() > 0); } -void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimeDirtyName, const bool selected) +void SelectDefaultApplication::addToMimetypeList(QListWidget *list, const QString &mimetypeName, const bool selected) { - // I didn't believe this was necessary, I tested, it is necessary. application/x-pkcs12 showed up here but is converted to application/pkcs12 - const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimeDirtyName); - const QString mimeName = mimetype.name(); - - QString name = mimetype.filterString().trimmed(); - if (name.isEmpty()) { - name = mimetype.comment().trimmed(); - } - if (name.isEmpty()) { - name = mimeName; - } else { - name += '\n' + mimeName; - } + QString name = mimetypeName; QListWidgetItem *item = new QListWidgetItem(name); - item->setData(Qt::UserRole, mimeName); - item->setIcon(m_mimeTypeIcons[mimeDirtyName]); + item->setData(Qt::UserRole, mimetypeName); + item->setIcon(m_mimeTypeIcons[mimetypeName]); list->addItem(item); item->setSelected(selected); } @@ -337,27 +325,35 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) for (const QString &readMimeName : mimetypes) { // Resolve aliases etc - const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); - if (!mimetype.isValid() && !readMimeName.startsWith("x-scheme-handler/")) { + const QMimeType mimetype = m_mimeDb.mimeTypeForData(readMimeName.trimmed()); + QString mimetypeName = mimetype.name(); + // There appears to be a bug in Qt https://bugreports.qt.io/browse/QTBUG-99509, hack around it + if (mimetypeName == "application/pkcs12") { + mimetypeName = "application/x-pkcs12"; + } else if (readMimeName.startsWith("x-scheme-handler/")) { + // x-scheme-handler is not a valid mimetype for a file, but we do want to be able to set applications as the default handlers for it. + // Assumes all x-scheme-handler/* is valid + mimetypeName = readMimeName.trimmed(); + } else if (mimetypeName == "") { + // An invalid QMimeType returns "" for .name(), so if it occurs then we should ignore it if (isVerbose) { - // TODO This happens a TON. Why? qDebug() << "In file " << appName << " mimetype " << readMimeName - << " is invalid. Ignoring..." << mimetype.name(); + << " is invalid. Ignoring..."; } continue; } - const QString mimetypeName = (mimetype.name() == "") ? readMimeName.trimmed() : mimetype.name(); -qDebug() << readMimeName << "Corresponds to" << mimetypeName; // Create a database of mimetypes this application is a child of // So applications that can edit parent mimetypes can also have associations formed to their child mimetypes // Unless the parent is 'application/octet-stream' because I guess a lot of stuff has that as its parent // Example: Kate editing text/plain can edit C source code - for (const QString &parent : mimetype.parentMimeTypes()) { - if (parent == "application/octet-stream") { - break; + if (mimetype.isValid()) { + for (const QString &parent : mimetype.parentMimeTypes()) { + if (parent == "application/octet-stream") { + break; + } + m_childMimeTypes.insert(parent, mimetypeName); } - m_childMimeTypes.insert(parent, mimetypeName); } if (mimetypeName.count('/') != 1) { From 78641dcc1eb2fa5f168e9083a9bb0c1afde14164 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 19:44:32 -0600 Subject: [PATCH 36/38] Fix Data to Name --- selectdefaultapplication.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index 81b9a17..f8cbe8b 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -325,7 +325,7 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) for (const QString &readMimeName : mimetypes) { // Resolve aliases etc - const QMimeType mimetype = m_mimeDb.mimeTypeForData(readMimeName.trimmed()); + const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); QString mimetypeName = mimetype.name(); // There appears to be a bug in Qt https://bugreports.qt.io/browse/QTBUG-99509, hack around it if (mimetypeName == "application/pkcs12") { From 680bed43f6eccb20e55806f5cbb23912cb87f791 Mon Sep 17 00:00:00 2001 From: magnus Date: Thu, 30 Dec 2021 20:00:50 -0600 Subject: [PATCH 37/38] Allow reading of x-scheme-handler full support --- selectdefaultapplication.cpp | 52 +++++++++++++++++++++--------------- selectdefaultapplication.h | 1 + 2 files changed, 32 insertions(+), 21 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index f8cbe8b..cd044d9 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -44,6 +44,7 @@ SelectDefaultApplication::SelectDefaultApplication(QWidget *parent, bool isVerbo if (m_mimeTypeIcons.contains(mimetypeName)) { continue; } + // Here we actually want to use the real mimetype, because we need to access its iconName const QMimeType mimetype = m_mimeDb.mimeTypeForName(mimetypeName); QString iconName = mimetype.iconName(); @@ -325,16 +326,8 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) for (const QString &readMimeName : mimetypes) { // Resolve aliases etc - const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); - QString mimetypeName = mimetype.name(); - // There appears to be a bug in Qt https://bugreports.qt.io/browse/QTBUG-99509, hack around it - if (mimetypeName == "application/pkcs12") { - mimetypeName = "application/x-pkcs12"; - } else if (readMimeName.startsWith("x-scheme-handler/")) { - // x-scheme-handler is not a valid mimetype for a file, but we do want to be able to set applications as the default handlers for it. - // Assumes all x-scheme-handler/* is valid - mimetypeName = readMimeName.trimmed(); - } else if (mimetypeName == "") { + QString mimetypeName = wrapperMimeTypeForName(readMimeName); + if (mimetypeName == "") { // An invalid QMimeType returns "" for .name(), so if it occurs then we should ignore it if (isVerbose) { qDebug() << "In file " << appName << " mimetype " << readMimeName @@ -347,13 +340,13 @@ void SelectDefaultApplication::loadDesktopFile(const QFileInfo &fileInfo) // So applications that can edit parent mimetypes can also have associations formed to their child mimetypes // Unless the parent is 'application/octet-stream' because I guess a lot of stuff has that as its parent // Example: Kate editing text/plain can edit C source code - if (mimetype.isValid()) { - for (const QString &parent : mimetype.parentMimeTypes()) { - if (parent == "application/octet-stream") { - break; - } - m_childMimeTypes.insert(parent, mimetypeName); + const QMimeType mimetype = m_mimeDb.mimeTypeForName(readMimeName.trimmed()); + // if !mimetype.isValid() this just won't enter the loop + for (const QString &parent : mimetype.parentMimeTypes()) { + if (parent == "application/octet-stream") { + break; } + m_childMimeTypes.insert(parent, mimetypeName); } if (mimetypeName.count('/') != 1) { @@ -417,16 +410,16 @@ void SelectDefaultApplication::setDefault(const QString &appName, const QSet > m_apps; From d5bbf1074f39179b431e1568355c31de0d1f677a Mon Sep 17 00:00:00 2001 From: magnus Date: Sun, 2 Jan 2022 21:03:50 -0600 Subject: [PATCH 38/38] Make labels less verbose, drastically increasing the length of a program name that needs to be clicked to do an annoying relayout --- selectdefaultapplication.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/selectdefaultapplication.cpp b/selectdefaultapplication.cpp index c88ebff..47c47a9 100644 --- a/selectdefaultapplication.cpp +++ b/selectdefaultapplication.cpp @@ -190,8 +190,8 @@ void SelectDefaultApplication::onApplicationSelectedLogic(bool allowEnabled) const QString appName = item->data(0).toString(); // Set banners and right widget - m_middleBanner->setText(appName + tr(" can open these filetypes:")); - m_rightBanner->setText(tr("Configured mimetypes ") + appName + tr(" will open:")); + m_middleBanner->setText(appName + tr(" can open:")); + m_rightBanner->setText(appName + tr(" currently opens:")); m_currentDefaultApps->clear(); for (QString &mimetype : m_defaultApps.keys(appName)) { addToMimetypeList(m_currentDefaultApps, mimetype, false);