/* GIO - GLib Input, Output and Streaming Library * * Copyright (C) 2006-2007 Red Hat, Inc. * Copyright (C) 2008 Hans Breuer * * SPDX-License-Identifier: LGPL-2.1-or-later * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with this library; if not, see . * * Author: Alexander Larsson * David Zeuthen * Hans Breuer */ #include "config.h" #include #define WIN32_MEAN_AND_LEAN #define COBJMACROS #include #include #include /* At the moment of writing IExtractIconW interface in Mingw-w64 * is missing IUnknown members in its vtable. Use our own * fixed declaration for now. */ #undef INTERFACE #define INTERFACE IMyExtractIconW DECLARE_INTERFACE_(IMyExtractIconW,IUnknown) { /*** IUnknown methods ***/ STDMETHOD_(HRESULT,QueryInterface)(THIS_ REFIID riid, void** ppvObject) PURE; STDMETHOD_(ULONG,AddRef)(THIS) PURE; STDMETHOD_(ULONG,Release)(THIS) PURE; /*** IMyExtractIconW methods ***/ STDMETHOD(GetIconLocation)(THIS_ UINT uFlags, LPWSTR pszIconFile, UINT cchMax, int *piIndex, PUINT pwFlags) PURE; STDMETHOD(Extract)(THIS_ LPCWSTR pszFile, UINT nIconIndex, HICON *phiconLarge, HICON *phiconSmall, UINT nIconSize) PURE; }; #undef INTERFACE #if !defined(__cplusplus) || defined(CINTERFACE) /*** IUnknown methods ***/ #define IMyExtractIconW_QueryInterface(p,a,b) (p)->lpVtbl->QueryInterface(p,a,b) #define IMyExtractIconW_AddRef(p) (p)->lpVtbl->AddRef(p) #define IMyExtractIconW_Release(p) (p)->lpVtbl->Release(p) /*** IMyExtractIconW methods ***/ #define IMyExtractIconW_GetIconLocation(p,a,b,c,d,e) (p)->lpVtbl->GetIconLocation(p,a,b,c,d,e) #define IMyExtractIconW_Extract(p,a,b,c,d,e) (p)->lpVtbl->Extract(p,a,b,c,d,e) #else /*** IUnknown methods ***/ #define IMyExtractIconW_QueryInterface(p,a,b) (p)->QueryInterface(a,b) #define IMyExtractIconW_AddRef(p) (p)->AddRef() #define IMyExtractIconW_Release(p) (p)->Release() /*** IMyExtractIconW methods ***/ #define IMyExtractIconW_GetIconLocation(p,a,b,c,d,e) (p)->GetIconLocation(p,a,b,c,d,e) #define IMyExtractIconW_Extract(p,a,b,c,d,e) (p)->Extract(p,a,b,c,d,e) #endif #include #include "gwin32volumemonitor.h" #include "gwin32mount.h" #include "gmount.h" #include "gfile.h" #include "gmountprivate.h" #include "gvolumemonitor.h" #include "gthemedicon.h" #include "glibintl.h" struct _GWin32Mount { GObject parent; GVolumeMonitor *volume_monitor; GWin32Volume *volume; /* owned by volume monitor */ int drive_type; /* why does all this stuff need to be duplicated? It is in volume already! */ char *name; GIcon *icon; GIcon *symbolic_icon; char *mount_path; gboolean can_eject; }; static void g_win32_mount_mount_iface_init (GMountIface *iface); #define g_win32_mount_get_type _g_win32_mount_get_type G_DEFINE_TYPE_WITH_CODE (GWin32Mount, g_win32_mount, G_TYPE_OBJECT, G_IMPLEMENT_INTERFACE (G_TYPE_MOUNT, g_win32_mount_mount_iface_init)) static void g_win32_mount_finalize (GObject *object) { GWin32Mount *mount; mount = G_WIN32_MOUNT (object); if (mount->volume_monitor != NULL) g_object_unref (mount->volume_monitor); #if 0 if (mount->volume) _g_win32_volume_unset_mount (mount->volume, mount); #endif /* TODO: g_warn_if_fail (volume->volume == NULL); */ if (mount->icon != NULL) g_object_unref (mount->icon); if (mount->symbolic_icon != NULL) g_object_unref (mount->symbolic_icon); g_free (mount->name); g_free (mount->mount_path); if (G_OBJECT_CLASS (g_win32_mount_parent_class)->finalize) (*G_OBJECT_CLASS (g_win32_mount_parent_class)->finalize) (object); } static void g_win32_mount_class_init (GWin32MountClass *klass) { GObjectClass *gobject_class = G_OBJECT_CLASS (klass); gobject_class->finalize = g_win32_mount_finalize; } static void g_win32_mount_init (GWin32Mount *win32_mount) { } /* wdrive doesn't need to end with a path separator. wdrive must use backslashes as path separators, not slashes. IShellFolder::ParseDisplayName() takes non-const string as input, so wdrive can't be a const string. Returns the name on success (free with g_free), NULL otherwise. */ static gchar * get_mount_display_name (gunichar2 *wdrive) { IShellFolder *desktop; PIDLIST_RELATIVE volume; STRRET volume_name; gchar *result = NULL; /* Get the desktop folder object reference */ if (!SUCCEEDED (SHGetDesktopFolder (&desktop))) return result; if (SUCCEEDED (IShellFolder_ParseDisplayName (desktop, NULL, NULL, wdrive, NULL, &volume, NULL))) { volume_name.uType = STRRET_WSTR; if (SUCCEEDED (IShellFolder_GetDisplayNameOf (desktop, volume, SHGDN_FORADDRESSBAR, &volume_name))) { wchar_t *volume_name_wchar; if (SUCCEEDED (StrRetToStrW (&volume_name, volume, &volume_name_wchar))) { result = g_utf16_to_utf8 (volume_name_wchar, -1, NULL, NULL, NULL); CoTaskMemFree (volume_name_wchar); } } CoTaskMemFree (volume); } IShellFolder_Release (desktop); return result; } static gchar * _win32_get_displayname (const char *drive) { gunichar2 *wdrive = g_utf8_to_utf16 (drive, -1, NULL, NULL, NULL); gchar *name = get_mount_display_name (wdrive); g_free (wdrive); return name ? name : g_strdup (drive); } /* * _g_win32_mount_new: * @volume_monitor: a #GVolumeMonitor. * @path: a win32 path. * @volume: usually NULL * * Returns: a #GWin32Mount for the given win32 path. **/ GWin32Mount * _g_win32_mount_new (GVolumeMonitor *volume_monitor, const char *path, GWin32Volume *volume) { GWin32Mount *mount; const gchar *drive = path; //fixme WCHAR *drive_utf16; drive_utf16 = g_utf8_to_utf16 (drive, -1, NULL, NULL, NULL); #if 0 /* No volume for mount: Ignore internal things */ if (volume == NULL && !g_win32_mount_guess_should_display (mount_entry)) return NULL; #endif mount = g_object_new (G_TYPE_WIN32_MOUNT, NULL); mount->volume_monitor = volume_monitor != NULL ? g_object_ref (volume_monitor) : NULL; mount->mount_path = g_strdup (path); mount->drive_type = GetDriveTypeW (drive_utf16); mount->can_eject = FALSE; /* TODO */ mount->name = _win32_get_displayname (drive); /* need to do this last */ mount->volume = volume; #if 0 if (volume != NULL) _g_win32_volume_set_mount (volume, mount); #endif g_free (drive_utf16); return mount; } void _g_win32_mount_unmounted (GWin32Mount *mount) { if (mount->volume != NULL) { #if 0 _g_win32_volume_unset_mount (mount->volume, mount); #endif mount->volume = NULL; g_signal_emit_by_name (mount, "changed"); /* there's really no need to emit mount_changed on the volume monitor * as we're going to be deleted.. */ } } void _g_win32_mount_unset_volume (GWin32Mount *mount, GWin32Volume *volume) { if (mount->volume == volume) { mount->volume = NULL; /* TODO: Emit changed in idle to avoid locking issues */ g_signal_emit_by_name (mount, "changed"); if (mount->volume_monitor != NULL) g_signal_emit_by_name (mount->volume_monitor, "mount-changed", mount); } } static GFile * g_win32_mount_get_root (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); return g_file_new_for_path (win32_mount->mount_path); } static const char * _win32_drive_type_to_icon (int type, gboolean use_symbolic) { switch (type) { case DRIVE_REMOVABLE : return use_symbolic ? "drive-removable-media-symbolic" : "drive-removable-media"; case DRIVE_FIXED : return use_symbolic ? "drive-harddisk-symbolic" : "drive-harddisk"; case DRIVE_REMOTE : return use_symbolic ? "folder-remote-symbolic" : "folder-remote"; case DRIVE_CDROM : return use_symbolic ? "drive-optical-symbolic" : "drive-optical"; default : return use_symbolic ? "folder-symbolic" : "folder"; } } /* mount_path doesn't need to end with a path separator. mount_path must use backslashes as path separators, not slashes. IShellFolder::ParseDisplayName() takes non-const string as input, so mount_path can't be a const string. result_name and result_index must not be NULL. Returns TRUE when result_name is set (free with g_free), FALSE otherwise. */ static gboolean get_icon_name_index (wchar_t *mount_path, wchar_t **result_name, int *result_index) { IShellFolder *desktop; PIDLIST_RELATIVE volume; IShellFolder *volume_parent; PCUITEMID_CHILD volume_relative; IMyExtractIconW *eicon; int icon_index; UINT icon_flags; wchar_t *name_buffer; gsize name_buffer_size; gsize arbitrary_reasonable_limit = 5000; gboolean result = FALSE; *result_name = NULL; /* Get the desktop folder object reference */ if (!SUCCEEDED (SHGetDesktopFolder (&desktop))) return FALSE; /* Construct the volume IDList relative to desktop */ if (SUCCEEDED (IShellFolder_ParseDisplayName (desktop, NULL, NULL, mount_path, NULL, &volume, NULL))) { /* Get the parent of the volume (transfer-full) and the IDList relative to parent (transfer-none) */ if (SUCCEEDED (SHBindToParent (volume, &IID_IShellFolder, (void **) &volume_parent, &volume_relative))) { /* Get a reference to IExtractIcon object for the volume */ if (SUCCEEDED (IShellFolder_GetUIObjectOf (volume_parent, NULL, 1, (LPCITEMIDLIST *) &volume_relative, &IID_IExtractIconW, NULL, (void **) &eicon))) { gboolean keep_going = TRUE; name_buffer = NULL; name_buffer_size = MAX_PATH / 2; while (keep_going) { name_buffer_size *= 2; name_buffer = g_renew (wchar_t, name_buffer, name_buffer_size); name_buffer[name_buffer_size - 1] = 0x1; /* sentinel */ keep_going = FALSE; /* Try to get the icon location */ if (SUCCEEDED (IMyExtractIconW_GetIconLocation (eicon, GIL_FORSHELL, name_buffer, name_buffer_size, &icon_index, &icon_flags))) { if (name_buffer[name_buffer_size - 1] != 0x1) { if (name_buffer_size < arbitrary_reasonable_limit) { /* Buffer was too small, keep going */ keep_going = TRUE; continue; } /* Else stop trying */ } /* name_buffer might not contain a name */ else if ((icon_flags & GIL_NOTFILENAME) != GIL_NOTFILENAME) { *result_name = g_steal_pointer (&name_buffer); *result_index = icon_index; result = TRUE; } } } g_free (name_buffer); IMyExtractIconW_Release (eicon); } IShellFolder_Release (volume_parent); } CoTaskMemFree (volume); } IShellFolder_Release (desktop); return result; } static GIcon * g_win32_mount_get_icon (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); g_return_val_if_fail (win32_mount->mount_path != NULL, NULL); /* lazy creation */ if (!win32_mount->icon) { wchar_t *icon_path; int icon_index; wchar_t *p; wchar_t *wfn = g_utf8_to_utf16 (win32_mount->mount_path, -1, NULL, NULL, NULL); for (p = wfn; p != NULL && *p != 0; p++) if (*p == L'/') *p = L'\\'; if (get_icon_name_index (wfn, &icon_path, &icon_index)) { gchar *id = g_strdup_printf ("%S,%i", icon_path, icon_index); g_free (icon_path); win32_mount->icon = g_themed_icon_new (id); g_free (id); } else { win32_mount->icon = g_themed_icon_new_with_default_fallbacks (_win32_drive_type_to_icon (win32_mount->drive_type, FALSE)); } g_free (wfn); } return g_object_ref (win32_mount->icon); } static GIcon * g_win32_mount_get_symbolic_icon (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); g_return_val_if_fail (win32_mount->mount_path != NULL, NULL); /* lazy creation */ if (!win32_mount->symbolic_icon) { win32_mount->symbolic_icon = g_themed_icon_new_with_default_fallbacks (_win32_drive_type_to_icon (win32_mount->drive_type, TRUE)); } return g_object_ref (win32_mount->symbolic_icon); } static char * g_win32_mount_get_uuid (GMount *mount) { return NULL; } static char * g_win32_mount_get_name (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); return g_strdup (win32_mount->name); } static GDrive * g_win32_mount_get_drive (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); if (win32_mount->volume != NULL) return g_volume_get_drive (G_VOLUME (win32_mount->volume)); return NULL; } static GVolume * g_win32_mount_get_volume (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); if (win32_mount->volume) return G_VOLUME (g_object_ref (win32_mount->volume)); return NULL; } static gboolean g_win32_mount_can_unmount (GMount *mount) { return FALSE; } static gboolean g_win32_mount_can_eject (GMount *mount) { GWin32Mount *win32_mount = G_WIN32_MOUNT (mount); return win32_mount->can_eject; } typedef struct { GWin32Mount *win32_mount; GAsyncReadyCallback callback; gpointer user_data; GCancellable *cancellable; int error_fd; GIOChannel *error_channel; guint error_channel_source_id; GString *error_string; } UnmountEjectOp; static void g_win32_mount_unmount (GMount *mount, GMountUnmountFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { } static gboolean g_win32_mount_unmount_finish (GMount *mount, GAsyncResult *result, GError **error) { return FALSE; } static void g_win32_mount_eject (GMount *mount, GMountUnmountFlags flags, GCancellable *cancellable, GAsyncReadyCallback callback, gpointer user_data) { } static gboolean g_win32_mount_eject_finish (GMount *mount, GAsyncResult *result, GError **error) { return FALSE; } static void g_win32_mount_mount_iface_init (GMountIface *iface) { iface->get_root = g_win32_mount_get_root; iface->get_name = g_win32_mount_get_name; iface->get_icon = g_win32_mount_get_icon; iface->get_symbolic_icon = g_win32_mount_get_symbolic_icon; iface->get_uuid = g_win32_mount_get_uuid; iface->get_drive = g_win32_mount_get_drive; iface->get_volume = g_win32_mount_get_volume; iface->can_unmount = g_win32_mount_can_unmount; iface->can_eject = g_win32_mount_can_eject; iface->unmount = g_win32_mount_unmount; iface->unmount_finish = g_win32_mount_unmount_finish; iface->eject = g_win32_mount_eject; iface->eject_finish = g_win32_mount_eject_finish; }