Created
August 1, 2025 04:06
-
-
Save konsolebox/d55f28bc391af8e1b8d470c672c14575 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # Copyright (c) 2025 konsolebox | |
| # | |
| # Permission is hereby granted, free of charge, to any person obtaining a copy | |
| # of this software and associated documentation files (the "Software"), to deal | |
| # in the Software without restriction, including without limitation the rights | |
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
| # copies of the Software, and to permit persons to whom the Software is | |
| # furnished to do so, subject to the following conditions: | |
| # | |
| # The above copyright notice and this permission notice shall be included in all | |
| # copies or substantial portions of the Software. | |
| # | |
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
| # SOFTWARE. | |
| # Developed with assistance from Grok, created by xAI | |
| require 'ffi' | |
| require 'optparse' | |
| require 'fileutils' | |
| # Extend FFI to interface with libxfce4util and glib | |
| module LibXfce4Util | |
| extend FFI::Library | |
| ffi_lib 'libxfce4util.so.7' # Adjust version if needed (check /usr/lib/libxfce4util.so*) | |
| # gboolean xfce_g_file_set_trusted(GFile *file, gboolean is_trusted, GCancellable *cancellable, GError **error) | |
| attach_function :xfce_g_file_set_trusted, [:pointer, :bool, :pointer, :pointer], :bool | |
| # gboolean xfce_g_file_is_trusted(GFile *file, GCancellable *cancellable, GError **error) | |
| attach_function :xfce_g_file_is_trusted, [:pointer, :pointer, :pointer], :bool | |
| end | |
| module GLib | |
| extend FFI::Library | |
| ffi_lib 'libgio-2.0.so.0' # GLib/GIO library | |
| # GFile* g_file_new_for_path(const char *path) | |
| attach_function :g_file_new_for_path, [:string], :pointer | |
| # void g_object_unref(gpointer object) | |
| attach_function :g_object_unref, [:pointer], :void | |
| end | |
| # Function to validate .desktop file | |
| def valid_desktop_file?(file) | |
| return false unless file =~ /\.desktop$/ | |
| content = File.read(file) rescue nil | |
| return false unless content | |
| return false unless content.match?(/^\[Desktop Entry\]/) | |
| return false unless content.match?(/^Type=Application/) | |
| return false unless content.match?(/^Exec=/) | |
| true | |
| end | |
| # Function to set executable bit | |
| def set_executable(file) | |
| if File.executable?(file) | |
| return true | |
| end | |
| begin | |
| File.chmod(0755, file) | |
| true | |
| rescue | |
| $stderr.puts "[ERROR] Failed to set executable bit on #{file}" | |
| false | |
| end | |
| end | |
| # Function to check trust status using xfce_g_file_is_trusted | |
| def get_trust_status(file) | |
| begin | |
| gfile = GLib.g_file_new_for_path(file) | |
| unless gfile | |
| $stderr.puts "[ERROR] Failed to create GFile for #{file}" | |
| return nil | |
| end | |
| error_ptr = FFI::MemoryPointer.new(:pointer, 1) | |
| error_ptr.write_pointer(nil) | |
| cancellable = nil # NULL for GCancellable | |
| result = LibXfce4Util.xfce_g_file_is_trusted(gfile, cancellable, error_ptr) | |
| GLib.g_object_unref(gfile) | |
| if error_ptr.read_pointer.null? | |
| return result ? "trusted" : "untrusted" | |
| else | |
| $stderr.puts "[ERROR] Failed to check trust status for #{file}" | |
| return nil | |
| end | |
| rescue | |
| $stderr.puts "[ERROR] Failed to call xfce_g_file_is_trusted for #{file}; trying to use fallback" | |
| return get_trust_status_fallback(file) | |
| end | |
| end | |
| # Fallback function to check trust status using gio | |
| def get_trust_status_fallback(file) | |
| unless system('command -v gio >/dev/null 2>&1') | |
| $stderr.puts "[ERROR] 'gio' not found. Install 'libglib2.0-bin' (Debian/Ubuntu) or 'glib2' (Fedora)." | |
| return nil | |
| end | |
| output = `gio info "#{file}" 2>/dev/null | grep metadata::xfce-exe-checksum` | |
| if $?.success? && output.match?(/metadata::xfce-exe-checksum:\s*[0-9a-f]{64}/) | |
| return "trusted" | |
| else | |
| return "untrusted" | |
| end | |
| end | |
| # Function to set trust status using xfce_g_file_set_trusted | |
| def set_trust_attribute(file, is_trusted) | |
| begin | |
| gfile = GLib.g_file_new_for_path(file) | |
| unless gfile | |
| $stderr.puts "[ERROR] Failed to create GFile for #{file}" | |
| return false | |
| end | |
| error_ptr = FFI::MemoryPointer.new(:pointer, 1) | |
| error_ptr.write_pointer(nil) | |
| cancellable = nil # NULL for GCancellable | |
| result = LibXfce4Util.xfce_g_file_set_trusted(gfile, is_trusted, cancellable, error_ptr) | |
| GLib.g_object_unref(gfile) | |
| if result | |
| tag = is_trusted ? "TRUSTED" : "UNTRUSTED" | |
| puts "[#{tag}] #{file}" | |
| return true | |
| else | |
| $stderr.puts "[ERROR] Failed to set trust status for #{file}" | |
| return false | |
| end | |
| rescue | |
| $stderr.puts "[ERROR] Failed to call xfce_g_file_set_trusted for #{file}; trying to use fallback" | |
| return set_trust_attribute_fallback(file, is_trusted) | |
| end | |
| end | |
| # Fallback function to set/unset metadata::xfce-exe-checksum using gio | |
| def set_trust_attribute_fallback(file, is_trusted) | |
| unless system('command -v gio >/dev/null 2>&1') | |
| $stderr.puts "[ERROR] 'gio' not found. Install 'libglib2.0-bin' (Debian/Ubuntu) or 'glib2' (Fedora)." | |
| return false | |
| end | |
| if is_trusted | |
| unless system('command -v sha256sum >/dev/null 2>&1') | |
| $stderr.puts "[ERROR] 'sha256sum' not found. Install 'coreutils'." | |
| return false | |
| end | |
| hash = `sha256sum "#{file}" 2>/dev/null`.split.first | |
| unless hash && hash.match?(/^[0-9a-f]{64}$/) | |
| $stderr.puts "[ERROR] Failed to compute SHA-256 hash for #{file}" | |
| return false | |
| end | |
| if system("gio set '#{file}' metadata::xfce-exe-checksum '#{hash}' 2>/dev/null") | |
| puts "[TRUSTED] #{file}" | |
| true | |
| else | |
| $stderr.puts "[ERROR] Failed to set metadata::xfce-exe-checksum on #{file}" | |
| false | |
| end | |
| else | |
| if system("gio set -t unset '#{file}' metadata::xfce-exe-checksum 2>/dev/null") | |
| puts "[UNTRUSTED] #{file}" | |
| true | |
| else | |
| $stderr.puts "[ERROR] Failed to unset metadata::xfce-exe-checksum on #{file}" | |
| false | |
| end | |
| end | |
| end | |
| # Function to reload xfdesktop | |
| def reload_desktop | |
| if system('command -v xfdesktop >/dev/null 2>&1') | |
| system('xfdesktop --reload 2>/dev/null') | |
| else | |
| $stderr.puts "[ERROR] 'xfdesktop' not found. Refresh manually." | |
| end | |
| end | |
| # Function for interactive confirmation | |
| def confirm_action(file) | |
| loop do | |
| print "[CONFIRM] Allow trust change for #{file}? (y/n): " | |
| response = gets.chomp.downcase | |
| return true if response == 'y' | |
| return false if response == 'n' | |
| puts "[ERROR] Please enter 'y' or 'n'." | |
| end | |
| end | |
| # Function to show usage info | |
| def show_usage_info_and_exit | |
| $stderr.puts <<~HELP | |
| Usage: xfce-desktop-file-tool.rb <subcommand> [options] <file1> [file2 ...] | |
| Subcommands: | |
| get-trusted Check trust status of .desktop files | |
| set-trusted Set trust status of .desktop files | |
| Options for set-trusted: | |
| -i, --interactive Prompt for confirmation before setting trust | |
| -r, --reload-desktop Reload xfdesktop after setting trust | |
| -u, --untrusted Mark files as untrusted (opposite of trusted) | |
| General options: | |
| -h, --help Show this help message | |
| HELP | |
| exit 0 | |
| end | |
| # Parse command-line options | |
| def parse_options | |
| options = { subcommand: nil, interactive: false, reload: false, untrusted: false } | |
| ARGV.each_with_index do |arg, i| | |
| if arg == 'get-trusted' || arg == 'set-trusted' | |
| options[:subcommand] = arg | |
| ARGV.delete_at(i) | |
| break | |
| end | |
| end | |
| OptionParser.new do |opts| | |
| opts.on('-i', '--interactive', 'Prompt for confirmation') { options[:interactive] = true } | |
| opts.on('-r', '--reload-desktop', 'Reload xfdesktop') { options[:reload] = true } | |
| opts.on('-u', '--untrusted', 'Mark as untrusted') { options[:untrusted] = true } | |
| opts.on('-h', '--help', 'Show help') { show_usage_info_and_exit } | |
| end.parse! | |
| options | |
| end | |
| # Main script | |
| options = parse_options | |
| unless options[:subcommand] | |
| $stderr.puts "[ERROR] No subcommand specified. Use get-trusted or set-trusted." | |
| show_usage_info_and_exit | |
| end | |
| if ARGV.empty? | |
| $stderr.puts "[ERROR] No files specified." | |
| show_usage_info_and_exit | |
| end | |
| case options[:subcommand] | |
| when 'get-trusted' | |
| ARGV.each do |file| | |
| unless File.exist?(file) | |
| $stderr.puts "[ERROR] #{file} does not exist" | |
| next | |
| end | |
| unless valid_desktop_file?(file) | |
| $stderr.puts "[ERROR] #{file} is not a valid .desktop file" | |
| next | |
| end | |
| status = get_trust_status(file) | |
| puts "#{file}: #{status}" if status | |
| end | |
| when 'set-trusted' | |
| ARGV.each do |file| | |
| unless File.exist?(file) | |
| $stderr.puts "[ERROR] #{file} does not exist" | |
| next | |
| end | |
| unless valid_desktop_file?(file) | |
| $stderr.puts "[ERROR] #{file} is not a valid .desktop file" | |
| next | |
| end | |
| status = get_trust_status(file) | |
| if status == "trusted" && !options[:untrusted] | |
| puts "[ALREADY_TRUSTED] #{file}" | |
| next | |
| elsif status == "untrusted" && options[:untrusted] | |
| puts "[ALREADY_UNTRUSTED] #{file}" | |
| next | |
| end | |
| if options[:interactive] | |
| next unless confirm_action(file) | |
| end | |
| unless options[:untrusted] | |
| unless set_executable(file) | |
| next | |
| end | |
| end | |
| set_trust_attribute(file, !options[:untrusted]) | |
| end | |
| reload_desktop if options[:reload] | |
| end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment