File: windows.py

package info (click to toggle)
syncthing-gtk 0.9.4.4%2Bds%2Bgit20221205%2B12a9702d29ab-2
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 2,888 kB
  • sloc: python: 8,077; sh: 259; xml: 134; makefile: 6
file content (240 lines) | stat: -rw-r--r-- 8,041 bytes parent folder | download | duplicates (2)
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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
#!/usr/bin/env python3
"""
Syncthing-GTK - Windows related stuff.
"""


import codecs
import logging
import msvcrt
import os
import winreg

import win32api
import win32pipe
import win32process
from gi.repository import Gdk, GLib, Gtk
from win32com.shell import shell, shellcon


log = logging.getLogger("windows.py")

SM_SHUTTINGDOWN = 0x2000


def fix_localized_system_error_messages():
    """
    Python has trouble decoding messages like
    'S?bor, ktor? u? existuje, sa ned? vytvori:'
    as they are encoded in some crazy, Windows-specific, locale-specific,
    day-in-week-specific encoding.

    This simply eats exceptions caused by 'ascii' codec and replaces
    non-decodable characters by question mark.
    """

    def handle_error(error):
        return ('?', error.end)

    codecs.register_error("strict", handle_error)


def enable_localization():
    """
    Updates environment variables with windows locale.
    """
    loc = "en"
    try:
        import locale
        loc = locale.getdefaultlocale()[0]
    except Exception:
        pass
    if 'LANGUAGE' not in os.environ:
        os.environ['LANGUAGE'] = loc


def is_shutting_down():
    """ Returns True if Windows initiated shutdown process """
    return (win32api.GetSystemMetrics(SM_SHUTTINGDOWN) != 0)


def nice_to_priority_class(nice):
    """ Converts nice value to windows priority class """
    if nice <= -20:  # PRIORITY_HIGHEST
        return win32process.HIGH_PRIORITY_CLASS,
    if nice <= -10:  # PRIORITY_HIGH
        return win32process.ABOVE_NORMAL_PRIORITY_CLASS
    if nice >= 10:  # PRIORITY_LOW
        return win32process.BELOW_NORMAL_PRIORITY_CLASS
    if nice >= 19:  # PRIORITY_LOWEST
        return win32process.IDLE_PRIORITY_CLASS
    # PRIORITY_NORMAL
    return win32process.NORMAL_PRIORITY_CLASS


def override_menu_borders():
    """ Loads custom CSS to create borders around popup menus """
    style_provider = Gtk.CssProvider()
    style_provider.load_from_data("""
        .menu {
            border-image: linear-gradient(to top,
                                          alpha(@borders, 0.80),
                                          alpha(@borders, 0.60) 33%,
                                          alpha(@borders, 0.50) 66%,
                                          alpha(@borders, 0.15)) 2 2 2 2/ 2px 2px 2px 2px;
        }

        .menubar .menu {
            border-image: linear-gradient(to top,
                                          alpha(@borders, 0.80),
                                          alpha(@borders, 0.60) 33%,
                                          alpha(@borders, 0.50) 66%,
                                          transparent 99%) 2 2 2 2/ 2px 2px 2px 2px;
        }
        """)
    Gtk.StyleContext.add_provider_for_screen(
        Gdk.Screen.get_default(),
        style_provider,
        Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
    )


def get_unicode_home():
    return shell.SHGetFolderPath(0, shellcon.CSIDL_LOCAL_APPDATA, None, 0)


class WinPopenReader:
    """
    Reads from PIPE using GLib timers or idle_add. Emulates part of
    UnixInputStream, but its in no way even close to complete
    emulation.

    This is only way that I found so far to have pipe and hidden
    console window on Windows.
    """

    def __init__(self, pipe):
        # Prepare stuff
        self._pipe = pipe
        self._waits_for_read = None
        self._buffer = ""
        self._buffer_size = 32
        self._closed = False
        self._osfhandle = msvcrt.get_osfhandle(self._pipe.fileno())
        # Start reading
        GLib.idle_add(self._peek)

    def _peek(self):
        if self._closed:
            return False
        # Check if there is anything to read and read if available
        (read, nAvail, nMessage) = win32pipe.PeekNamedPipe(self._osfhandle, 0)
        if nAvail >= self._buffer_size:
            data = self._pipe.read(self._buffer_size)
            self._buffer += data
        # If there is read_async callback and buffer has some data,
        # send them right away
        if self._waits_for_read is not None and len(self._buffer) > 0:
            r = WinPopenReader.Results(self._buffer)
            self._buffer = ""
            callback, data = self._waits_for_read
            self._waits_for_read = None
            callback(self, r, *data)
            GLib.idle_add(self._peek)
            return False
        GLib.timeout_add_seconds(1, self._peek)
        return False

    def read_bytes_async(self, size, trash, cancel, callback, data=()):
        if self._waits_for_read is not None:
            raise Exception("Already reading")
        self._buffer_size = size
        self._waits_for_read = (callback, data)

    def read_bytes_finish(self, results):
        return results

    def close(self):
        self._closed = True

    class Results:
        """ Also serves as response object """

        def __init__(self, data):
            self._data = data

        def get_data(self):
            return self._data


def WinConfiguration():
    from syncthing_gtk.configuration import _Configuration, serializer

    class _WinConfiguration(_Configuration):
        """
        Configuration implementation for Windows - stores values
        in registry
        """

        # @ Overrides
        def load(self):
            self.values = {}
            r = winreg.OpenKey(winreg.HKEY_CURRENT_USER,
                               "Software\\SyncthingGTK")
            for key in _Configuration.REQUIRED_KEYS:
                tp, trash = _Configuration.REQUIRED_KEYS[key]
                try:
                    self.values[key] = self._read(r, key, tp)
                except WindowsError:
                    # Not found
                    pass
            winreg.CloseKey(r)

        # @ Overrides
        def save(self):
            r = winreg.CreateKey(winreg.HKEY_CURRENT_USER,
                                 "Software\\SyncthingGTK")
            for key in _Configuration.REQUIRED_KEYS:
                tp, trash = _Configuration.REQUIRED_KEYS[key]
                value = self.values[key]
                self._store(r, key, tp, value)
            winreg.CloseKey(r)

        def _store(self, r, name, tp, value):
            """ Stores value in registry, handling special types """
            if tp == str:
                winreg.SetValueEx(r, name, 0, winreg.REG_SZ, str(value))
            elif tp in (int, bool):
                value = int(value)
                if value > 0xFFFF:
                    raise ValueError("Overflow")
                if value < 0:
                    # This basically prevents storing anything >0xFFFF to registry.
                    # Luckily, that shouldn't be needed, largest thing stored as int is 20
                    value = 0xFFFF + (-value)
                winreg.SetValueEx(r, name, 0, winreg.REG_DWORD, int(value))
            elif tp in (list, tuple):
                if value is not None:  # None is default value for window_position
                    winreg.SetValueEx(r, "%s_size" %
                                      (name,), 0, winreg.REG_DWORD, len(value))
                    for i in range(0, len(value)):
                        self._store(r, "%s_%s" % (name, i),
                                    type(value[i]), value[i])
            else:
                winreg.SetValueEx(r, name, 0, winreg.REG_SZ, serializer(value))

        def _read(self, r, name, tp):
            """ Reads value from registry, handling special types """
            if tp in (list, tuple):
                size, trash = winreg.QueryValueEx(r, "%s_size" % (name,))
                value = []
                for i in range(0, size):
                    value.append(self._read(r, "%s_%s" % (name, i), None))
                return value
            else:
                value, keytype = winreg.QueryValueEx(r, name)
                if type(value) == int and value > 0xFFFF:
                    value = - (value - 0xFFFF)
                return value

    return _WinConfiguration()