/* ADW-DING: Desktop Icons New Generation for GNOME Shell * * Adw/Gtk4 Port Copyright (C) 2025 Sundeep Mediratta (smedius@gmail.com) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, version 3 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import {gettext, Gio, GLib, Gtk} from '../../dependencies/gi.js'; import {_} from '../../dependencies/gettext.js'; import {Enums} from '../../dependencies/localFiles.js'; export {DesktopFolderUtils}; const DesktopFolderUtils = class { constructor(window) { this._activeWindow = window; this.Enums = Enums; this._desktopDir = this.getDesktopDir(); // Bind xdg-user-dirs translation domain gettext.bindtextdomain('xdg-user-dirs', '/usr/share/locale'); } _monitorDesktopDirChanges() { this._xdgUserDirs = this._getXdgUserDirs(); this._monitorXdgUserDirs = this._xdgUserDirs.monitor_file( Gio.FileMonitorFlags.WATCH_MOVES, null); this._monitorXdgUserDirs.set_rate_limit(2000); this._connectMonitor(); } _connectMonitor() { this._monitorID = this._monitorXdgUserDirs.connect( 'changed', (obj, file, otherFile, event) => { if (!(event === Gio.FileMonitorEvent.CHANGES_DONE_HINT || event === Gio.FileMonitorEvent.RENAMED)) return; if (this._changingDesktopDirID) GLib.source_remove(this._changingDesktopDirID); this._changingDesktopDirID = GLib.timeout_add(GLib.PRIORITY_LOW, 500, () => { const newDesktopDir = this.getDesktopDir(); this.onDesktopFolderChanged(newDesktopDir); this._changingDesktopDirID = null; return GLib.SOURCE_REMOVE; }); } ); } _disconnectMonitor() { if (this._monitorID) this._monitorXdgUserDirs.disconnect(this._monitorID); this._monitorID = 0; } _stopMonitoring() { this._disconnectMonitor(); this._monitorXdgUserDirs?.cancel(); } onDesktopFolderChanged(newDesktopDir) { this._desktopDir = newDesktopDir; } changeDesktop() { const dialog = new Gtk.FileDialog(); dialog.set_title(_('Choose Desktop Folder')); dialog.set_accept_label(_('Choose')); dialog.set_modal(true); dialog.set_initial_folder( Gio.File.new_for_commandline_arg(GLib.get_home_dir()) ); dialog.select_folder( this.activeWindow, null, this._finishChooseDesktopFolder.bind(this) ); } restoreDefaultDesktop() { const localizedDesktopName = this.getSystemLocalizedDesktopDir(); const defaultDesktop = GLib.build_filenamev([GLib.get_home_dir(), localizedDesktopName]); const desktopFolder = Gio.File.new_for_path(defaultDesktop); try { if (!desktopFolder.query_exists(null)) GLib.mkdir_with_parents(desktopFolder.get_path(), 0o755); } catch (e) { console.error( `Unable to create Folder ${defaultDesktop}: ${e.message}` ); } this._setDesktopFolder(desktopFolder); } getDesktopDir() { const glibDesktopPath = GLib.get_user_special_dir(GLib.UserDirectory.DIRECTORY_DESKTOP); let xdgDesktopPath = null; try { const userDirsGioFile = this._getXdgUserDirs(); if (!userDirsGioFile.query_exists(null)) { throw new Error( 'User configuration file user-dirs.users does not exist' ); } const decoder = new TextDecoder(); const contents = decoder .decode(GLib.file_get_contents(userDirsGioFile.get_path())[1]) .trim(); if (contents) xdgDesktopPath = this._parseUserDirsFile(contents); } catch (e) { console.error(e, `XDG Desktop not set, ${e}`); } const desktopPath = xdgDesktopPath ? xdgDesktopPath : glibDesktopPath; return Gio.File.new_for_commandline_arg(desktopPath); } getSystemLocalizedDesktopDir() { const systemDesktopDirName = this._getSystemDesktopDir() ?? this.Enums.DEFAULT_DESKTOP_NAME; const localizedDesktopName = gettext.dgettext('xdg-user-dirs', systemDesktopDirName); return localizedDesktopName ?? systemDesktopDirName; } _getSystemDesktopDir() { const systemDirsGioFile = this._getXdgSystemDirs(); if (!systemDirsGioFile) { console.error('No system xdg user-dirs.default file'); return null; } let xdgSystemDesktopPath = null; const decoder = new TextDecoder(); try { const contents = decoder .decode(GLib.file_get_contents(systemDirsGioFile.get_path())[1]) .trim(); if (contents) { const parseSystemconfig = true; xdgSystemDesktopPath = this._parseUserDirsFile(contents, parseSystemconfig); } } catch (e) { console.error(e, `XDG Desktop not set in user-dirs.default, ${e}`); } return xdgSystemDesktopPath; } async _finishChooseDesktopFolder(dialog, asyncResult) { let newFolder = null; try { newFolder = dialog.select_folder_finish(asyncResult); } catch (e) { if (e.matches(Gtk.DialogError, Gtk.DialogError.CANCELLED) || e.matches(Gtk.DialogError, Gtk.DialogError.DISMISSED)) return; console.error(e, `Error selecting folder: ${e.message}`); } if (!newFolder) return; await this._setDesktopFolder(newFolder); } async _setDesktopFolder(newFolder) { const isFolder = newFolder.query_file_type( Gio.FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null ) === Gio.FileType.DIRECTORY; if (!isFolder) return; if (newFolder.get_path() === this._desktopDir.get_path()) return; await this._writeXdgUserDirsDesktopFile(newFolder.get_path()); } _isDefaultDesktopFolder() { const localizedDesktopName = this.getSystemLocalizedDesktopDir(); const defaultDesktop = GLib.build_filenamev([GLib.get_home_dir(), localizedDesktopName]); return this._desktopDir.get_path() === defaultDesktop; } _parseUserDirsFile(content, systemconfig = null) { if (!content) return null; const serarchstring = systemconfig ? 'DESKTOP=' : 'XDG_DESKTOP_DIR='; const lineArray = content.trim().split('\n'); const desktopline = lineArray.filter(l => l.startsWith(serarchstring))[0]; let xdgDesktopPath = desktopline.split('=')[1].trim(); xdgDesktopPath = xdgDesktopPath.replace(/^"|"$/g, ''); xdgDesktopPath = xdgDesktopPath.replace('$HOME', GLib.get_home_dir()); return xdgDesktopPath; } async _writeXdgUserDirsDesktopFile(path) { const userDirsGioFile = this._getXdgUserDirs(); if (path.startsWith(GLib.get_home_dir())) path = path.replace(GLib.get_home_dir(), '$HOME'); const newline = `XDG_DESKTOP_DIR="${path}"`; try { const decoder = new TextDecoder(); const contents = decoder .decode(GLib.file_get_contents(userDirsGioFile.get_path())[1]) .trim(); const lineArray = contents.split('\n'); const newArray = lineArray.map(l => { if (l.startsWith('XDG_DESKTOP_DIR=')) return newline; return l; }); const newContents = newArray.join('\n'); await this._replaceFileContentsAsync(userDirsGioFile, newContents); } catch (e) { console.error(e, `Failed to write XDG Desktop file with ${e}`); } } async _replaceFileContentsAsync(destinationFile, contents) { const textCoder = new TextEncoder(); const byteArray = new GLib.Bytes(textCoder.encode(contents)); try { await destinationFile.replace_contents_async( byteArray, null, false, Gio.FileCreateFlags.REPLACE_DESTINATION, null ); } catch (e) { if (e.matches( Gio.IOErrorEnum, Gio.IOErrorEnum.NOT_EMPTY )) { GLib.mkdir_with_parents( GLib.path_get_dirname(destinationFile.get_path()), 0o700 ); this._replaceFileContentsAsync(destinationFile, contents); return; } console.error(e, `Failed to write XDG Desktop file with ${e}`); } } _getXdgUserDirs() { const xdgUserDirspath = Gio.File.new_build_filenamev( [GLib.get_user_config_dir(), this.Enums.XDG_USER_DIRS] ); return xdgUserDirspath; } _getXdgSystemDirs() { const xdgSystemDirsArray = GLib.get_system_config_dirs(); for (let dir of xdgSystemDirsArray) { const xdgSystemdir = Gio.File.new_build_filenamev( [dir, this.Enums.XDG_SYSTEM_DIRS] ); if (xdgSystemdir.query_exists(null)) return xdgSystemdir; } return null; } get activeWindow() { if (this._activeWindow) return this._activeWindow; return Gio.Application.get_default().get_active_window(); } set activeWindow(window) { this._activeWindow = window; } get isDefaultDesktop() { return this._isDefaultDesktopFolder(); } };