Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
glib_loop.py295 linesDownload Raw Back to event_loop
1# Urwid main loop code2#    Copyright (C) 2004-2012  Ian Ward3#    Copyright (C) 2008 Walter Mundt4#    Copyright (C) 2009 Andrew Psaltis5#6#    This library is free software; you can redistribute it and/or7#    modify it under the terms of the GNU Lesser General Public8#    License as published by the Free Software Foundation; either9#    version 2.1 of the License, or (at your option) any later version.10#11#    This library is distributed in the hope that it will be useful,12#    but WITHOUT ANY WARRANTY; without even the implied warranty of13#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU14#    Lesser General Public License for more details.15#16#    You should have received a copy of the GNU Lesser General Public17#    License along with this library; if not, write to the Free Software18#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA19#20# Urwid web site: https://urwid.org/21 22"""GLib based urwid EventLoop implementation.23 24PyGObject library is required.25"""26 27from __future__ import annotations28 29import functools30import logging31import signal32import typing33 34from gi.repository import GLib35 36from .abstract_loop import EventLoop, ExitMainLoop37 38if typing.TYPE_CHECKING:39    from collections.abc import Callable40    from concurrent.futures import Executor, Future41    from types import FrameType42 43    from typing_extensions import Literal, ParamSpec44 45    _Spec = ParamSpec("_Spec")46    _T = typing.TypeVar("_T")47 48__all__ = ("GLibEventLoop",)49 50 51def _ignore_handler(_sig: int, _frame: FrameType | None = None) -> None:52    return None53 54 55class GLibEventLoop(EventLoop):56    """57    Event loop based on GLib.MainLoop58    """59 60    def __init__(self) -> None:61        super().__init__()62        self.logger = logging.getLogger(__name__).getChild(self.__class__.__name__)63        self._alarms: list[int] = []64        self._watch_files: dict[int, int] = {}65        self._idle_handle: int = 066        self._glib_idle_enabled = False  # have we called glib.idle_add?67        self._idle_callbacks: dict[int, Callable[[], typing.Any]] = {}68        self._loop = GLib.MainLoop()69        self._exc: BaseException | None = None70        self._enable_glib_idle()71        self._signal_handlers: dict[int, int] = {}72 73    def run_in_executor(74        self,75        executor: Executor,76        func: Callable[_Spec, _T],77        *args: _Spec.args,78        **kwargs: _Spec.kwargs,79    ) -> Future[_T]:80        """Run callable in executor.81 82        :param executor: Executor to use for running the function83        :type executor: concurrent.futures.Executor84        :param func: function to call85        :type func: Callable86        :param args: positional arguments to function87        :type args: object88        :param kwargs: keyword arguments to function89        :type kwargs: object90        :return: future object for the function call outcome.91        :rtype: concurrent.futures.Future92        """93        return executor.submit(func, *args, **kwargs)94 95    def alarm(96        self,97        seconds: float,98        callback: Callable[[], typing.Any],99    ) -> tuple[int, Callable[[], typing.Any]]:100        """101        Call callback() a given time from now.  No parameters are102        passed to callback.103 104        Returns a handle that may be passed to remove_alarm()105 106        seconds -- floating point time to wait before calling callback107        callback -- function to call from event loop108        """109 110        @self.handle_exit111        def ret_false() -> Literal[False]:112            callback()113            self._enable_glib_idle()114            return False115 116        fd = GLib.timeout_add(int(seconds * 1000), ret_false)117        self._alarms.append(fd)118        return (fd, callback)119 120    def set_signal_handler(121        self,122        signum: int,123        handler: Callable[[int, FrameType | None], typing.Any] | int | signal.Handlers,124    ) -> None:125        """126        Sets the signal handler for signal signum.127 128        .. WARNING::129            Because this method uses the `GLib`-specific `unix_signal_add`130            function, its behaviour is different than `signal.signal().`131 132            If `signum` is not `SIGHUP`, `SIGINT`, `SIGTERM`, `SIGUSR1`,133            `SIGUSR2` or `SIGWINCH`, this method performs no actions and134            immediately returns None.135 136            Returns None in all cases (unlike :func:`signal.signal()`).137        ..138 139        signum -- signal number140        handler -- function (taking signum as its single argument),141        or `signal.SIG_IGN`, or `signal.SIG_DFL`142        """143        glib_signals = [144            signal.SIGHUP,145            signal.SIGINT,146            signal.SIGTERM,147            signal.SIGUSR1,148            signal.SIGUSR2,149        ]150 151        # GLib supports SIGWINCH as of version 2.54.152        if not GLib.check_version(2, 54, 0):153            glib_signals.append(signal.SIGWINCH)154 155        if signum not in glib_signals:156            # The GLib event loop supports only the signals listed above157            return158 159        if signum in self._signal_handlers:160            GLib.source_remove(self._signal_handlers.pop(signum))161 162        if handler == signal.Handlers.SIG_IGN:163            handler = _ignore_handler164        elif handler == signal.Handlers.SIG_DFL:165            return166 167        def final_handler(signal_number: int):168            # MyPy False-negative: signal.Handlers casted169            handler(signal_number, None)  # type: ignore[operator]170            return GLib.SOURCE_CONTINUE171 172        source = GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signum, final_handler, signum)173        self._signal_handlers[signum] = source174 175    def remove_alarm(self, handle) -> bool:176        """177        Remove an alarm.178 179        Returns True if the alarm exists, False otherwise180        """181        try:182            self._alarms.remove(handle[0])183            GLib.source_remove(handle[0])184 185        except ValueError:186            return False187 188        return True189 190    def watch_file(self, fd: int, callback: Callable[[], typing.Any]) -> int:191        """192        Call callback() when fd has some data to read.  No parameters193        are passed to callback.194 195        Returns a handle that may be passed to remove_watch_file()196 197        fd -- file descriptor to watch for input198        callback -- function to call when input is available199        """200 201        @self.handle_exit202        def io_callback(source, cb_condition) -> Literal[True]:203            callback()204            self._enable_glib_idle()205            return True206 207        self._watch_files[fd] = GLib.io_add_watch(fd, GLib.IO_IN, io_callback)208        return fd209 210    def remove_watch_file(self, handle: int) -> bool:211        """212        Remove an input file.213 214        Returns True if the input file exists, False otherwise215        """216        if handle in self._watch_files:217            GLib.source_remove(self._watch_files[handle])218            del self._watch_files[handle]219            return True220        return False221 222    def enter_idle(self, callback: Callable[[], typing.Any]) -> int:223        """224        Add a callback for entering idle.225 226        Returns a handle that may be passed to remove_enter_idle()227        """228        self._idle_handle += 1229        self._idle_callbacks[self._idle_handle] = callback230        return self._idle_handle231 232    def _enable_glib_idle(self) -> None:233        if self._glib_idle_enabled:234            return235        GLib.idle_add(self._glib_idle_callback)236        self._glib_idle_enabled = True237 238    def _glib_idle_callback(self):239        for callback in self._idle_callbacks.values():240            callback()241        self._glib_idle_enabled = False242        return False  # ask glib not to call again (or we would be called243 244    def remove_enter_idle(self, handle) -> bool:245        """246        Remove an idle callback.247 248        Returns True if the handle was removed.249        """250        try:251            del self._idle_callbacks[handle]252        except KeyError:253            return False254        return True255 256    def run(self) -> None:257        """258        Start the event loop.  Exit the loop when any callback raises259        an exception.  If ExitMainLoop is raised, exit cleanly.260        """261        try:262            self._loop.run()263        finally:264            if self._loop.is_running():265                self._loop.quit()266        if self._exc:267            # An exception caused us to exit, raise it now268            exc = self._exc269            self._exc = None270            raise exc.with_traceback(exc.__traceback__)271 272    def handle_exit(self, f: Callable[_Spec, _T]) -> Callable[_Spec, _T | Literal[False]]:273        """274        Decorator that cleanly exits the :class:`GLibEventLoop` if275        :exc:`ExitMainLoop` is thrown inside of the wrapped function. Store the276        exception info if some other exception occurs, it will be reraised after277        the loop quits.278 279        *f* -- function to be wrapped280        """281 282        @functools.wraps(f)283        def wrapper(*args: _Spec.args, **kwargs: _Spec.kwargs) -> _T | Literal[False]:284            try:285                return f(*args, **kwargs)286            except ExitMainLoop:287                self._loop.quit()288            except BaseException as exc:289                self._exc = exc290                if self._loop.is_running():291                    self._loop.quit()292            return False293 294        return wrapper295 
codekingpro/portable-devtools · Team Ai