WindowsRemovableDrivePlugin.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123
  1. # Copyright (c) 2015 Ultimaker B.V.
  2. # Copyright (c) 2013 David Braam
  3. # Uranium is released under the terms of the LGPLv3 or higher.
  4. from . import RemovableDrivePlugin
  5. import string
  6. import ctypes
  7. from ctypes import wintypes # Using ctypes.wintypes in the code below does not seem to work
  8. from UM.i18n import i18nCatalog
  9. catalog = i18nCatalog("cura")
  10. # Ignore windows error popups. Fixes the whole "Can't open drive X" when user has an SD card reader.
  11. ctypes.windll.kernel32.SetErrorMode(1) #type: ignore
  12. # WinAPI Constants that we need
  13. # Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.
  14. DRIVE_REMOVABLE = 2 # [CodeStyle: Windows Enum value]
  15. GENERIC_READ = 2147483648 # [CodeStyle: Windows Enum value]
  16. GENERIC_WRITE = 1073741824 # [CodeStyle: Windows Enum value]
  17. FILE_SHARE_READ = 1 # [CodeStyle: Windows Enum value]
  18. FILE_SHARE_WRITE = 2 # [CodeStyle: Windows Enum value]
  19. IOCTL_STORAGE_EJECT_MEDIA = 2967560 # [CodeStyle: Windows Enum value]
  20. OPEN_EXISTING = 3 # [CodeStyle: Windows Enum value]
  21. # Setup the DeviceIoControl function arguments and return type.
  22. # See ctypes documentation for details on how to call C functions from python, and why this is important.
  23. ctypes.windll.kernel32.DeviceIoControl.argtypes = [ #type: ignore
  24. wintypes.HANDLE, # _In_ HANDLE hDevice
  25. wintypes.DWORD, # _In_ DWORD dwIoControlCode
  26. wintypes.LPVOID, # _In_opt_ LPVOID lpInBuffer
  27. wintypes.DWORD, # _In_ DWORD nInBufferSize
  28. wintypes.LPVOID, # _Out_opt_ LPVOID lpOutBuffer
  29. wintypes.DWORD, # _In_ DWORD nOutBufferSize
  30. ctypes.POINTER(wintypes.DWORD), # _Out_opt_ LPDWORD lpBytesReturned
  31. wintypes.LPVOID # _Inout_opt_ LPOVERLAPPED lpOverlapped
  32. ]
  33. ctypes.windll.kernel32.DeviceIoControl.restype = wintypes.BOOL #type: ignore
  34. ## Removable drive support for windows
  35. class WindowsRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
  36. def checkRemovableDrives(self):
  37. drives = {}
  38. bitmask = ctypes.windll.kernel32.GetLogicalDrives()
  39. # Check possible drive letters, from A to Z
  40. # Note: using ascii_uppercase because we do not want this to change with locale!
  41. for letter in string.ascii_uppercase:
  42. drive = "{0}:/".format(letter)
  43. # Do we really want to skip A and B?
  44. # GetDriveTypeA explicitly wants a byte array of type ascii. It will accept a string, but this wont work
  45. if bitmask & 1 and ctypes.windll.kernel32.GetDriveTypeA(drive.encode("ascii")) == DRIVE_REMOVABLE:
  46. volume_name = ""
  47. name_buffer = ctypes.create_unicode_buffer(1024)
  48. filesystem_buffer = ctypes.create_unicode_buffer(1024)
  49. error = ctypes.windll.kernel32.GetVolumeInformationW(ctypes.c_wchar_p(drive), name_buffer, ctypes.sizeof(name_buffer), None, None, None, filesystem_buffer, ctypes.sizeof(filesystem_buffer))
  50. if error != 0:
  51. volume_name = name_buffer.value
  52. if not volume_name:
  53. volume_name = catalog.i18nc("@item:intext", "Removable Drive")
  54. # Certain readers will report themselves as a volume even when there is no card inserted, but will show an
  55. # "No volume in drive" warning when trying to call GetDiskFreeSpace. However, they will not report a valid
  56. # filesystem, so we can filter on that. In addition, this excludes other things with filesystems Windows
  57. # does not support.
  58. if filesystem_buffer.value == "":
  59. continue
  60. # Check for the free space. Some card readers show up as a drive with 0 space free when there is no card inserted.
  61. free_bytes = ctypes.c_longlong(0)
  62. if ctypes.windll.kernel32.GetDiskFreeSpaceExA(drive.encode("ascii"), ctypes.byref(free_bytes), None, None) == 0:
  63. continue
  64. if free_bytes.value < 1:
  65. continue
  66. drives[drive] = "{0} ({1}:)".format(volume_name, letter)
  67. bitmask >>= 1
  68. return drives
  69. def performEjectDevice(self, device):
  70. # Magic WinAPI stuff
  71. # First, open a handle to the Device
  72. handle = ctypes.windll.kernel32.CreateFileA("\\\\.\\{0}".format(device.getId()[:-1]).encode("ascii"), GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, None, OPEN_EXISTING, 0, None )
  73. if handle == -1:
  74. # ctypes.WinError sets up an GetLastError API call for windows as an Python OSError exception.
  75. # So we use this to raise the error to our caller.
  76. raise ctypes.WinError()
  77. # The DeviceIoControl requires a bytes_returned pointer to be a valid pointer.
  78. # So create a ctypes DWORD to reference. (Without this pointer the DeviceIoControl function will crash with an access violation after doing its job.
  79. bytes_returned = wintypes.DWORD(0)
  80. error = None
  81. # Then, try and tell it to eject
  82. return_code = ctypes.windll.kernel32.DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, None, 0, None, 0, ctypes.pointer(bytes_returned), None)
  83. # DeviceIoControl with IOCTL_STORAGE_EJECT_MEDIA return 0 on error.
  84. if return_code == 0:
  85. # ctypes.WinError sets up an GetLastError API call for windows as an Python OSError exception.
  86. # So we use this to raise the error to our caller.
  87. error = ctypes.WinError()
  88. # Do not raise an error here yet, so we can properly close the handle.
  89. # Finally, close the handle
  90. ctypes.windll.kernel32.CloseHandle(handle)
  91. # If an error happened in the DeviceIoControl, raise it now.
  92. if error:
  93. raise error
  94. # Return success
  95. return True