| /* 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 <http://www.gnu.org/licenses/>. | |
| * | |
| * Author: Alexander Larsson <alexl@redhat.com> | |
| * David Zeuthen <davidz@redhat.com> | |
| * Hans Breuer <hans@breuer.org> | |
| */ | |
| #include "config.h" | |
| #include <string.h> | |
| #define WIN32_MEAN_AND_LEAN | |
| #define COBJMACROS | |
| #include <windows.h> | |
| #include <shlobj.h> | |
| #include <shlwapi.h> | |
| /* 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 <glib.h> | |
| #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; | |
| } |