Welcome to mirror list, hosted at ThFree Co, Russian Federation.

WindowsRemovableDrivePlugin.py « RemovableDriveOutputDevice « plugins - github.com/Ultimaker/Cura.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
blob: 8a183c25f43e5c3f4dc51c0fe9f5738716701f1a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# Copyright (c) 2015 Ultimaker B.V.
# Copyright (c) 2013 David Braam
# Uranium is released under the terms of the LGPLv3 or higher.
from . import RemovableDrivePlugin

import string
import ctypes
from ctypes import wintypes  # Using ctypes.wintypes in the code below does not seem to work

from UM.i18n import i18nCatalog
catalog = i18nCatalog("cura")

# Ignore windows error popups. Fixes the whole "Can't open drive X" when user has an SD card reader.
ctypes.windll.kernel32.SetErrorMode(1) #type: ignore

# WinAPI Constants that we need
# Hardcoded here due to stupid WinDLL stuff that does not give us access to these values.
DRIVE_REMOVABLE = 2 # [CodeStyle: Windows Enum value]

GENERIC_READ = 2147483648 # [CodeStyle: Windows Enum value]
GENERIC_WRITE = 1073741824 # [CodeStyle: Windows Enum value]

FILE_SHARE_READ = 1 # [CodeStyle: Windows Enum value]
FILE_SHARE_WRITE = 2 # [CodeStyle: Windows Enum value]

IOCTL_STORAGE_EJECT_MEDIA = 2967560 # [CodeStyle: Windows Enum value]

OPEN_EXISTING = 3 # [CodeStyle: Windows Enum value]

# Setup the DeviceIoControl function arguments and return type.
# See ctypes documentation for details on how to call C functions from python, and why this is important.
ctypes.windll.kernel32.DeviceIoControl.argtypes = [ #type: ignore
    wintypes.HANDLE,                    # _In_          HANDLE hDevice
    wintypes.DWORD,                     # _In_          DWORD dwIoControlCode
    wintypes.LPVOID,                    # _In_opt_      LPVOID lpInBuffer
    wintypes.DWORD,                     # _In_          DWORD nInBufferSize
    wintypes.LPVOID,                    # _Out_opt_     LPVOID lpOutBuffer
    wintypes.DWORD,                     # _In_          DWORD nOutBufferSize
    ctypes.POINTER(wintypes.DWORD),     # _Out_opt_     LPDWORD lpBytesReturned
    wintypes.LPVOID                     # _Inout_opt_   LPOVERLAPPED lpOverlapped
]
ctypes.windll.kernel32.DeviceIoControl.restype = wintypes.BOOL #type: ignore


## Removable drive support for windows
class WindowsRemovableDrivePlugin(RemovableDrivePlugin.RemovableDrivePlugin):
    def checkRemovableDrives(self):
        drives = {}

        # The currently available disk drives, e.g.: bitmask = ...1100 <-- ...DCBA
        bitmask = ctypes.windll.kernel32.GetLogicalDrives()
        # Since we are ignoring drives A and B, the bitmask has has to shift twice to the right
        bitmask >>= 2
        # Check possible drive letters, from C to Z
        # Note: using ascii_uppercase because we do not want this to change with locale!
        # Skip A and B, since those drives are typically reserved for floppy disks.
        # Those drives can theoretically be reassigned but it's safer to not check them for removable drives.
        # Windows will also behave weirdly even with some of its internal functions if you do this (e.g. search indexing doesn't search it).
        # Users that have removable drives in A or B will just have to save to file and select the drive there.
        for letter in string.ascii_uppercase[2:]:
            drive = "{0}:/".format(letter)

            # Do we really want to skip A and B?
            # GetDriveTypeA explicitly wants a byte array of type ascii. It will accept a string, but this wont work
            if bitmask & 1 and ctypes.windll.kernel32.GetDriveTypeA(drive.encode("ascii")) == DRIVE_REMOVABLE:
                volume_name = ""
                name_buffer = ctypes.create_unicode_buffer(1024)
                filesystem_buffer = ctypes.create_unicode_buffer(1024)
                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))

                if error != 0:
                    volume_name = name_buffer.value

                if not volume_name:
                    volume_name = catalog.i18nc("@item:intext", "Removable Drive")

                # Certain readers will report themselves as a volume even when there is no card inserted, but will show an
                # "No volume in drive" warning when trying to call GetDiskFreeSpace. However, they will not report a valid
                # filesystem, so we can filter on that. In addition, this excludes other things with filesystems Windows
                # does not support.
                if filesystem_buffer.value == "":
                    continue

                # Check for the free space. Some card readers show up as a drive with 0 space free when there is no card inserted.
                free_bytes = ctypes.c_longlong(0)
                if ctypes.windll.kernel32.GetDiskFreeSpaceExA(drive.encode("ascii"), ctypes.byref(free_bytes), None, None) == 0:
                    continue

                if free_bytes.value < 1:
                    continue

                drives[drive] = "{0} ({1}:)".format(volume_name, letter)
            bitmask >>= 1

        return drives

    def performEjectDevice(self, device):
        # Magic WinAPI stuff
        # First, open a handle to the Device
        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 )

        if handle == -1:
            # ctypes.WinError sets up an GetLastError API call for windows as an Python OSError exception.
            # So we use this to raise the error to our caller.
            raise ctypes.WinError()

        # The DeviceIoControl requires a bytes_returned pointer to be a valid pointer.
        # So create a ctypes DWORD to reference. (Without this pointer the DeviceIoControl function will crash with an access violation after doing its job.
        bytes_returned = wintypes.DWORD(0)

        error = None

        # Then, try and tell it to eject
        return_code = ctypes.windll.kernel32.DeviceIoControl(handle, IOCTL_STORAGE_EJECT_MEDIA, None, 0, None, 0, ctypes.pointer(bytes_returned), None)
        # DeviceIoControl with IOCTL_STORAGE_EJECT_MEDIA return 0 on error.
        if return_code == 0:
            # ctypes.WinError sets up an GetLastError API call for windows as an Python OSError exception.
            # So we use this to raise the error to our caller.
            error = ctypes.WinError()
            # Do not raise an error here yet, so we can properly close the handle.

        # Finally, close the handle
        ctypes.windll.kernel32.CloseHandle(handle)

        # If an error happened in the DeviceIoControl, raise it now.
        if error:
            raise error

        # Return success
        return True