evdev 1.9.0__tar.gz → 1.9.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {evdev-1.9.0/src/evdev.egg-info → evdev-1.9.2}/PKG-INFO +3 -2
  2. {evdev-1.9.0 → evdev-1.9.2}/pyproject.toml +2 -2
  3. {evdev-1.9.0 → evdev-1.9.2}/setup.py +10 -3
  4. evdev-1.9.2/src/evdev/__init__.py +39 -0
  5. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/device.py +24 -16
  6. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/ecodes_runtime.py +1 -1
  7. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/eventio_async.py +47 -40
  8. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/genecodes_c.py +11 -6
  9. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/genecodes_py.py +2 -1
  10. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/util.py +2 -2
  11. {evdev-1.9.0 → evdev-1.9.2/src/evdev.egg-info}/PKG-INFO +3 -2
  12. {evdev-1.9.0 → evdev-1.9.2}/tests/test_ecodes.py +13 -3
  13. evdev-1.9.0/src/evdev/__init__.py +0 -9
  14. {evdev-1.9.0 → evdev-1.9.2}/LICENSE +0 -0
  15. {evdev-1.9.0 → evdev-1.9.2}/MANIFEST.in +0 -0
  16. {evdev-1.9.0 → evdev-1.9.2}/README.md +0 -0
  17. {evdev-1.9.0 → evdev-1.9.2}/setup.cfg +0 -0
  18. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/ecodes.py +0 -0
  19. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/eventio.py +0 -0
  20. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/events.py +0 -0
  21. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/evtest.py +0 -0
  22. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/ff.py +0 -0
  23. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/input.c +0 -0
  24. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/py.typed +0 -0
  25. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/uinput.c +0 -0
  26. {evdev-1.9.0 → evdev-1.9.2}/src/evdev/uinput.py +0 -0
  27. {evdev-1.9.0 → evdev-1.9.2}/src/evdev.egg-info/SOURCES.txt +0 -0
  28. {evdev-1.9.0 → evdev-1.9.2}/src/evdev.egg-info/dependency_links.txt +0 -0
  29. {evdev-1.9.0 → evdev-1.9.2}/src/evdev.egg-info/top_level.txt +0 -0
  30. {evdev-1.9.0 → evdev-1.9.2}/tests/test_events.py +0 -0
  31. {evdev-1.9.0 → evdev-1.9.2}/tests/test_uinput.py +0 -0
  32. {evdev-1.9.0 → evdev-1.9.2}/tests/test_util.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: evdev
3
- Version: 1.9.0
3
+ Version: 1.9.2
4
4
  Summary: Bindings to the Linux input handling subsystem
5
5
  Author-email: Georgi Valkov <georgi.t.valkov@gmail.com>
6
6
  Maintainer-email: Tobi <proxima@sezanzeb.de>
@@ -46,6 +46,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
46
46
  Requires-Python: >=3.8
47
47
  Description-Content-Type: text/markdown
48
48
  License-File: LICENSE
49
+ Dynamic: license-file
49
50
 
50
51
  # evdev
51
52
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "evdev"
7
- version = "1.9.0"
7
+ version = "1.9.2"
8
8
  description = "Bindings to the Linux input handling subsystem"
9
9
  keywords = ["evdev", "input", "uinput"]
10
10
  readme = "README.md"
@@ -36,7 +36,7 @@ line-length = 120
36
36
  ignore = ["E265", "E241", "F403", "F401", "E401", "E731"]
37
37
 
38
38
  [tool.bumpversion]
39
- current_version = "1.9.0"
39
+ current_version = "1.9.2"
40
40
  commit = true
41
41
  tag = true
42
42
  allow_dirty = true
@@ -14,7 +14,7 @@ curdir = Path(__file__).resolve().parent
14
14
  ecodes_c_path = curdir / "src/evdev/ecodes.c"
15
15
 
16
16
 
17
- def create_ecodes(headers=None):
17
+ def create_ecodes(headers=None, reproducible=False):
18
18
  if not headers:
19
19
  include_paths = set()
20
20
  cpath = os.environ.get("CPATH", "").strip()
@@ -65,7 +65,10 @@ def create_ecodes(headers=None):
65
65
 
66
66
  print("writing %s (using %s)" % (ecodes_c_path, " ".join(headers)))
67
67
  with ecodes_c_path.open("w") as fh:
68
- cmd = [sys.executable, "src/evdev/genecodes_c.py", "--ecodes", *headers]
68
+ cmd = [sys.executable, "src/evdev/genecodes_c.py"]
69
+ if reproducible:
70
+ cmd.append("--reproducible")
71
+ cmd.extend(["--ecodes", *headers])
69
72
  run(cmd, check=True, stdout=fh)
70
73
 
71
74
 
@@ -74,17 +77,21 @@ class build_ecodes(Command):
74
77
 
75
78
  user_options = [
76
79
  ("evdev-headers=", None, "colon-separated paths to input subsystem headers"),
80
+ ("reproducible", None, "hide host details (host/paths) to create a reproducible output"),
77
81
  ]
78
82
 
79
83
  def initialize_options(self):
80
84
  self.evdev_headers = None
85
+ self.reproducible = False
81
86
 
82
87
  def finalize_options(self):
83
88
  if self.evdev_headers:
84
89
  self.evdev_headers = self.evdev_headers.split(":")
90
+ if self.reproducible is None:
91
+ self.reproducible = False
85
92
 
86
93
  def run(self):
87
- create_ecodes(self.evdev_headers)
94
+ create_ecodes(self.evdev_headers, reproducible=self.reproducible)
88
95
 
89
96
 
90
97
  class build_ext(_build_ext.build_ext):
@@ -0,0 +1,39 @@
1
+ # --------------------------------------------------------------------------
2
+ # Gather everything into a single, convenient namespace.
3
+ # --------------------------------------------------------------------------
4
+
5
+ # The superfluous "import name as name" syntax is here to satisfy mypy's attrs-defined rule.
6
+ # Alternatively all exported objects can be listed in __all__.
7
+
8
+ from . import (
9
+ ecodes as ecodes,
10
+ ff as ff,
11
+ )
12
+
13
+ from .device import (
14
+ AbsInfo as AbsInfo,
15
+ DeviceInfo as DeviceInfo,
16
+ EvdevError as EvdevError,
17
+ InputDevice as InputDevice,
18
+ )
19
+
20
+ from .events import (
21
+ AbsEvent as AbsEvent,
22
+ InputEvent as InputEvent,
23
+ KeyEvent as KeyEvent,
24
+ RelEvent as RelEvent,
25
+ SynEvent as SynEvent,
26
+ event_factory as event_factory,
27
+ )
28
+
29
+ from .uinput import (
30
+ UInput as UInput,
31
+ UInputError as UInputError,
32
+ )
33
+
34
+ from .util import (
35
+ categorize as categorize,
36
+ list_devices as list_devices,
37
+ resolve_ecodes as resolve_ecodes,
38
+ resolve_ecodes_dict as resolve_ecodes_dict,
39
+ )
@@ -1,6 +1,6 @@
1
1
  import contextlib
2
2
  import os
3
- from typing import NamedTuple, Tuple, Union
3
+ from typing import Dict, Generic, Iterator, List, Literal, NamedTuple, Tuple, TypeVar, Union, overload
4
4
 
5
5
  from . import _input, ecodes, util
6
6
 
@@ -9,6 +9,8 @@ try:
9
9
  except ImportError:
10
10
  from .eventio import EvdevError, EventIO
11
11
 
12
+ _AnyStr = TypeVar("_AnyStr", str, bytes)
13
+
12
14
 
13
15
  class AbsInfo(NamedTuple):
14
16
  """Absolute axis information.
@@ -95,19 +97,19 @@ class DeviceInfo(NamedTuple):
95
97
  product: int
96
98
  version: int
97
99
 
98
- def __str__(self):
100
+ def __str__(self) -> str:
99
101
  msg = "bus: {:04x}, vendor {:04x}, product {:04x}, version {:04x}"
100
102
  return msg.format(*self) # pylint: disable=not-an-iterable
101
103
 
102
104
 
103
- class InputDevice(EventIO):
105
+ class InputDevice(EventIO, Generic[_AnyStr]):
104
106
  """
105
107
  A linux input device from which input events can be read.
106
108
  """
107
109
 
108
110
  __slots__ = ("path", "fd", "info", "name", "phys", "uniq", "_rawcapabilities", "version", "ff_effects_count")
109
111
 
110
- def __init__(self, dev: Union[str, bytes, os.PathLike]):
112
+ def __init__(self, dev: Union[_AnyStr, "os.PathLike[_AnyStr]"]):
111
113
  """
112
114
  Arguments
113
115
  ---------
@@ -116,7 +118,7 @@ class InputDevice(EventIO):
116
118
  """
117
119
 
118
120
  #: Path to input device.
119
- self.path = dev if not hasattr(dev, "__fspath__") else dev.__fspath__()
121
+ self.path: _AnyStr = dev if not hasattr(dev, "__fspath__") else dev.__fspath__()
120
122
 
121
123
  # Certain operations are possible only when the device is opened in read-write mode.
122
124
  try:
@@ -151,7 +153,7 @@ class InputDevice(EventIO):
151
153
  #: The number of force feedback effects the device can keep in its memory.
152
154
  self.ff_effects_count = _input.ioctl_EVIOCGEFFECTS(self.fd)
153
155
 
154
- def __del__(self):
156
+ def __del__(self) -> None:
155
157
  if hasattr(self, "fd") and self.fd is not None:
156
158
  try:
157
159
  self.close()
@@ -176,7 +178,13 @@ class InputDevice(EventIO):
176
178
 
177
179
  return res
178
180
 
179
- def capabilities(self, verbose: bool = False, absinfo: bool = True):
181
+ @overload
182
+ def capabilities(self, verbose: Literal[False] = ..., absinfo: bool = ...) -> Dict[int, List[int]]:
183
+ ...
184
+ @overload
185
+ def capabilities(self, verbose: Literal[True], absinfo: bool = ...) -> Dict[Tuple[str, int], List[Tuple[str, int]]]:
186
+ ...
187
+ def capabilities(self, verbose: bool = False, absinfo: bool = True) -> Union[Dict[int, List[int]], Dict[Tuple[str, int], List[Tuple[str, int]]]]:
180
188
  """
181
189
  Return the event types that this device supports as a mapping of
182
190
  supported event types to lists of handled event codes.
@@ -263,7 +271,7 @@ class InputDevice(EventIO):
263
271
 
264
272
  return leds
265
273
 
266
- def set_led(self, led_num: int, value: int):
274
+ def set_led(self, led_num: int, value: int) -> None:
267
275
  """
268
276
  Set the state of the selected LED.
269
277
 
@@ -279,18 +287,18 @@ class InputDevice(EventIO):
279
287
  """
280
288
  return isinstance(other, self.__class__) and self.info == other.info and self.path == other.path
281
289
 
282
- def __str__(self):
290
+ def __str__(self) -> str:
283
291
  msg = 'device {}, name "{}", phys "{}", uniq "{}"'
284
292
  return msg.format(self.path, self.name, self.phys, self.uniq or "")
285
293
 
286
- def __repr__(self):
294
+ def __repr__(self) -> str:
287
295
  msg = (self.__class__.__name__, self.path)
288
296
  return "{}({!r})".format(*msg)
289
297
 
290
298
  def __fspath__(self):
291
299
  return self.path
292
300
 
293
- def close(self):
301
+ def close(self) -> None:
294
302
  if self.fd > -1:
295
303
  try:
296
304
  super().close()
@@ -298,7 +306,7 @@ class InputDevice(EventIO):
298
306
  finally:
299
307
  self.fd = -1
300
308
 
301
- def grab(self):
309
+ def grab(self) -> None:
302
310
  """
303
311
  Grab input device using ``EVIOCGRAB`` - other applications will
304
312
  be unable to receive events until the device is released. Only
@@ -311,7 +319,7 @@ class InputDevice(EventIO):
311
319
 
312
320
  _input.ioctl_EVIOCGRAB(self.fd, 1)
313
321
 
314
- def ungrab(self):
322
+ def ungrab(self) -> None:
315
323
  """
316
324
  Release device if it has been already grabbed (uses `EVIOCGRAB`).
317
325
 
@@ -324,7 +332,7 @@ class InputDevice(EventIO):
324
332
  _input.ioctl_EVIOCGRAB(self.fd, 0)
325
333
 
326
334
  @contextlib.contextmanager
327
- def grab_context(self):
335
+ def grab_context(self) -> Iterator[None]:
328
336
  """
329
337
  A context manager for the duration of which only the current
330
338
  process will be able to receive events from the device.
@@ -342,7 +350,7 @@ class InputDevice(EventIO):
342
350
  ff_id = _input.upload_effect(self.fd, data)
343
351
  return ff_id
344
352
 
345
- def erase_effect(self, ff_id):
353
+ def erase_effect(self, ff_id) -> None:
346
354
  """
347
355
  Erase a force effect from a force feedback device. This also
348
356
  stops the effect.
@@ -402,7 +410,7 @@ class InputDevice(EventIO):
402
410
  """
403
411
  return AbsInfo(*_input.ioctl_EVIOCGABS(self.fd, axis_num))
404
412
 
405
- def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None):
413
+ def set_absinfo(self, axis_num: int, value=None, min=None, max=None, fuzz=None, flat=None, resolution=None) -> None:
406
414
  """
407
415
  Update :class:`AbsInfo` values. Only specified values will be overwritten.
408
416
 
@@ -46,7 +46,7 @@ from . import _ecodes
46
46
  #: Mapping of names to values.
47
47
  ecodes = {}
48
48
 
49
- prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP".split()
49
+ prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF INPUT_PROP UI_FF".split()
50
50
  prev_prefix = ""
51
51
  g = globals()
52
52
 
@@ -1,11 +1,57 @@
1
1
  import asyncio
2
2
  import select
3
+ import sys
3
4
 
4
5
  from . import eventio
6
+ from .events import InputEvent
5
7
 
6
8
  # needed for compatibility
7
9
  from .eventio import EvdevError
8
10
 
11
+ if sys.version_info >= (3, 11):
12
+ from typing import Self
13
+ else:
14
+ from typing import Any as Self
15
+
16
+
17
+ class ReadIterator:
18
+ def __init__(self, device):
19
+ self.current_batch = iter(())
20
+ self.device = device
21
+
22
+ # Standard iterator protocol.
23
+ def __iter__(self) -> Self:
24
+ return self
25
+
26
+ def __next__(self) -> InputEvent:
27
+ try:
28
+ # Read from the previous batch of events.
29
+ return next(self.current_batch)
30
+ except StopIteration:
31
+ r, w, x = select.select([self.device.fd], [], [])
32
+ self.current_batch = self.device.read()
33
+ return next(self.current_batch)
34
+
35
+ def __aiter__(self) -> Self:
36
+ return self
37
+
38
+ def __anext__(self) -> "asyncio.Future[InputEvent]":
39
+ future = asyncio.Future()
40
+ try:
41
+ # Read from the previous batch of events.
42
+ future.set_result(next(self.current_batch))
43
+ except StopIteration:
44
+
45
+ def next_batch_ready(batch):
46
+ try:
47
+ self.current_batch = batch.result()
48
+ future.set_result(next(self.current_batch))
49
+ except Exception as e:
50
+ future.set_exception(e)
51
+
52
+ self.device.async_read().add_done_callback(next_batch_ready)
53
+ return future
54
+
9
55
 
10
56
  class EventIO(eventio.EventIO):
11
57
  def _do_when_readable(self, callback):
@@ -42,7 +88,7 @@ class EventIO(eventio.EventIO):
42
88
  self._do_when_readable(lambda: self._set_result(future, self.read))
43
89
  return future
44
90
 
45
- def async_read_loop(self):
91
+ def async_read_loop(self) -> ReadIterator:
46
92
  """
47
93
  Return an iterator that yields input events. This iterator is
48
94
  compatible with the ``async for`` syntax.
@@ -58,42 +104,3 @@ class EventIO(eventio.EventIO):
58
104
  # no event loop present, so there is nothing to
59
105
  # remove the reader from. Ignore
60
106
  pass
61
-
62
-
63
- class ReadIterator:
64
- def __init__(self, device):
65
- self.current_batch = iter(())
66
- self.device = device
67
-
68
- # Standard iterator protocol.
69
- def __iter__(self):
70
- return self
71
-
72
- def __next__(self):
73
- try:
74
- # Read from the previous batch of events.
75
- return next(self.current_batch)
76
- except StopIteration:
77
- r, w, x = select.select([self.device.fd], [], [])
78
- self.current_batch = self.device.read()
79
- return next(self.current_batch)
80
-
81
- def __aiter__(self):
82
- return self
83
-
84
- def __anext__(self):
85
- future = asyncio.Future()
86
- try:
87
- # Read from the previous batch of events.
88
- future.set_result(next(self.current_batch))
89
- except StopIteration:
90
-
91
- def next_batch_ready(batch):
92
- try:
93
- self.current_batch = batch.result()
94
- future.set_result(next(self.current_batch))
95
- except Exception as e:
96
- future.set_exception(e)
97
-
98
- self.device.async_read().add_done_callback(next_batch_ready)
99
- return future
@@ -15,22 +15,27 @@ headers = [
15
15
  "/usr/include/linux/uinput.h",
16
16
  ]
17
17
 
18
- opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs"])
18
+ opts, args = getopt.getopt(sys.argv[1:], "", ["ecodes", "stubs", "reproducible"])
19
19
  if not opts:
20
- print("usage: genecodes.py [--ecodes|--stubs] <headers>")
20
+ print("usage: genecodes.py [--ecodes|--stubs] [--reproducible] <headers>")
21
21
  exit(2)
22
22
 
23
23
  if args:
24
24
  headers = args
25
25
 
26
+ reproducible = ("--reproducible", "") in opts
27
+
26
28
 
27
29
  # -----------------------------------------------------------------------------
28
30
  macro_regex = r"#define\s+((?:KEY|ABS|REL|SW|MSC|LED|BTN|REP|SND|ID|EV|BUS|SYN|FF|UI_FF|INPUT_PROP)_\w+)"
29
31
  macro_regex = re.compile(macro_regex)
30
32
 
31
- # Uname without hostname.
32
- uname = list(os.uname())
33
- uname = " ".join((uname[0], *uname[2:]))
33
+ if reproducible:
34
+ uname = "hidden for reproducibility"
35
+ else:
36
+ # Uname without hostname.
37
+ uname = list(os.uname())
38
+ uname = " ".join((uname[0], *uname[2:]))
34
39
 
35
40
 
36
41
  # -----------------------------------------------------------------------------
@@ -138,5 +143,5 @@ elif ("--stubs", "") in opts:
138
143
  template = template_stubs
139
144
 
140
145
  body = os.linesep.join(body)
141
- text = template % (uname, headers, body)
146
+ text = template % (uname, headers if not reproducible else ["hidden for reproducibility"], body)
142
147
  print(text.strip())
@@ -40,6 +40,7 @@ entries = [
40
40
  ("BUS", "Dict[int, Union[str, Tuple[str]]]", None),
41
41
  ("SYN", "Dict[int, Union[str, Tuple[str]]]", None),
42
42
  ("FF", "Dict[int, Union[str, Tuple[str]]]", None),
43
+ ("UI_FF", "Dict[int, Union[str, Tuple[str]]]", None),
43
44
  ("FF_STATUS", "Dict[int, Union[str, Tuple[str]]]", None),
44
45
  ("INPUT_PROP", "Dict[int, Union[str, Tuple[str]]]", None)
45
46
  ]
@@ -50,4 +51,4 @@ for key, annotation, doc in entries:
50
51
 
51
52
  print(f"{key}: {annotation} = ", end="")
52
53
  pprint(getattr(ecodes, key))
53
- print()
54
+ print()
@@ -6,7 +6,7 @@ import stat
6
6
  from typing import Union, List
7
7
 
8
8
  from . import ecodes
9
- from .events import event_factory
9
+ from .events import InputEvent, event_factory, KeyEvent, RelEvent, AbsEvent, SynEvent
10
10
 
11
11
 
12
12
  def list_devices(input_device_dir: Union[str, bytes, os.PathLike] = "/dev/input") -> List[str]:
@@ -32,7 +32,7 @@ def is_device(fn: Union[str, bytes, os.PathLike]) -> bool:
32
32
  return True
33
33
 
34
34
 
35
- def categorize(event):
35
+ def categorize(event: InputEvent) -> Union[InputEvent, KeyEvent, RelEvent, AbsEvent, SynEvent]:
36
36
  """
37
37
  Categorize an event according to its type.
38
38
 
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: evdev
3
- Version: 1.9.0
3
+ Version: 1.9.2
4
4
  Summary: Bindings to the Linux input handling subsystem
5
5
  Author-email: Georgi Valkov <georgi.t.valkov@gmail.com>
6
6
  Maintainer-email: Tobi <proxima@sezanzeb.de>
@@ -46,6 +46,7 @@ Classifier: Programming Language :: Python :: Implementation :: CPython
46
46
  Requires-Python: >=3.8
47
47
  Description-Content-Type: text/markdown
48
48
  License-File: LICENSE
49
+ Dynamic: license-file
49
50
 
50
51
  # evdev
51
52
 
@@ -1,9 +1,8 @@
1
- # encoding: utf-8
2
-
3
1
  from evdev import ecodes
2
+ from evdev import ecodes_runtime
4
3
 
5
4
 
6
- prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF"
5
+ prefixes = "KEY ABS REL SW MSC LED BTN REP SND ID EV BUS SYN FF_STATUS FF UI_FF"
7
6
 
8
7
 
9
8
  def to_tuples(val):
@@ -29,3 +28,14 @@ def test_overlap():
29
28
  vals_ff = set(to_tuples(ecodes.FF.values()))
30
29
  vals_ff_status = set(to_tuples(ecodes.FF_STATUS.values()))
31
30
  assert bool(vals_ff & vals_ff_status) is False
31
+
32
+
33
+ def test_generated():
34
+ e_run = vars(ecodes_runtime)
35
+ e_gen = vars(ecodes)
36
+
37
+ def keys(v):
38
+ res = {k for k in v.keys() if not k.startswith("_") and not k[1].islower()}
39
+ return res
40
+
41
+ assert keys(e_run) == keys(e_gen)
@@ -1,9 +0,0 @@
1
- # --------------------------------------------------------------------------
2
- # Gather everything into a single, convenient namespace.
3
- # --------------------------------------------------------------------------
4
-
5
- from . import ecodes, ff
6
- from .device import AbsInfo, DeviceInfo, EvdevError, InputDevice
7
- from .events import AbsEvent, InputEvent, KeyEvent, RelEvent, SynEvent, event_factory
8
- from .uinput import UInput, UInputError
9
- from .util import categorize, list_devices, resolve_ecodes, resolve_ecodes_dict
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes