208 lines
7.3 KiB
Python
208 lines
7.3 KiB
Python
# ----------------------------------------------------------------------------
|
|
# pyglet
|
|
# Copyright (c) 2006-2008 Alex Holkner
|
|
# Copyright (c) 2008-2022 pyglet contributors
|
|
# All rights reserved.
|
|
#
|
|
# Redistribution and use in source and binary forms, with or without
|
|
# modification, are permitted provided that the following conditions
|
|
# are met:
|
|
#
|
|
# * Redistributions of source code must retain the above copyright
|
|
# notice, this list of conditions and the following disclaimer.
|
|
# * Redistributions in binary form must reproduce the above copyright
|
|
# notice, this list of conditions and the following disclaimer in
|
|
# the documentation and/or other materials provided with the
|
|
# distribution.
|
|
# * Neither the name of pyglet nor the names of its
|
|
# contributors may be used to endorse or promote products
|
|
# derived from this software without specific prior written
|
|
# permission.
|
|
#
|
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
# POSSIBILITY OF SUCH DAMAGE.
|
|
# ----------------------------------------------------------------------------
|
|
|
|
"""Fork a child process and inform it of mode changes to each screen. The
|
|
child waits until the parent process dies, and then connects to each X server
|
|
with a mode change and restores the mode.
|
|
|
|
This emulates the behaviour of Windows and Mac, so that resolution changes
|
|
made by an application are not permanent after the program exits, even if
|
|
the process is terminated uncleanly.
|
|
|
|
The child process is communicated to via a pipe, and watches for parent
|
|
death with a Linux extension signal handler.
|
|
"""
|
|
|
|
import ctypes
|
|
import os
|
|
import signal
|
|
import struct
|
|
import threading
|
|
|
|
from pyglet.libs.x11 import xlib
|
|
from pyglet.util import asbytes
|
|
|
|
try:
|
|
from pyglet.libs.x11 import xf86vmode
|
|
except:
|
|
# No xf86vmode... should not be switching modes.
|
|
pass
|
|
|
|
_restore_mode_child_installed = False
|
|
_restorable_screens = set()
|
|
_mode_write_pipe = None
|
|
|
|
|
|
# Mode packets tell the child process how to restore a given display and
|
|
# screen. Only one packet should be sent per display/screen (more would
|
|
# indicate redundancy or incorrect restoration). Packet format is:
|
|
# display (max 256 chars),
|
|
# screen
|
|
# width
|
|
# height
|
|
# rate
|
|
class ModePacket:
|
|
format = '256siHHI'
|
|
size = struct.calcsize(format)
|
|
|
|
def __init__(self, display, screen, width, height, rate):
|
|
self.display = display
|
|
self.screen = screen
|
|
self.width = width
|
|
self.height = height
|
|
self.rate = rate
|
|
|
|
def encode(self):
|
|
return struct.pack(self.format, self.display, self.screen, self.width, self.height, self.rate)
|
|
|
|
@classmethod
|
|
def decode(cls, data):
|
|
display, screen, width, height, rate = struct.unpack(cls.format, data)
|
|
return cls(display.strip(asbytes('\0')), screen, width, height, rate)
|
|
|
|
def __repr__(self):
|
|
return '%s(%r, %r, %r, %r, %r)' % (
|
|
self.__class__.__name__, self.display, self.screen,
|
|
self.width, self.height, self.rate)
|
|
|
|
def set(self):
|
|
display = xlib.XOpenDisplay(self.display)
|
|
modes, n_modes = get_modes_array(display, self.screen)
|
|
mode = get_matching_mode(modes, n_modes, self.width, self.height, self.rate)
|
|
if mode is not None:
|
|
xf86vmode.XF86VidModeSwitchToMode(display, self.screen, mode)
|
|
free_modes_array(modes, n_modes)
|
|
xlib.XCloseDisplay(display)
|
|
|
|
|
|
def get_modes_array(display, screen):
|
|
count = ctypes.c_int()
|
|
modes = ctypes.POINTER(ctypes.POINTER(xf86vmode.XF86VidModeModeInfo))()
|
|
xf86vmode.XF86VidModeGetAllModeLines(display, screen, count, modes)
|
|
return modes, count.value
|
|
|
|
|
|
def get_matching_mode(modes, n_modes, width, height, rate):
|
|
# Copy modes out of list and free list
|
|
for i in range(n_modes):
|
|
mode = modes.contents[i]
|
|
if (mode.hdisplay == width and
|
|
mode.vdisplay == height and
|
|
mode.dotclock == rate):
|
|
return mode
|
|
return None
|
|
|
|
|
|
def free_modes_array(modes, n_modes):
|
|
for i in range(n_modes):
|
|
mode = modes.contents[i]
|
|
if mode.privsize:
|
|
xlib.XFree(mode.private)
|
|
xlib.XFree(modes)
|
|
|
|
|
|
def _install_restore_mode_child():
|
|
global _mode_write_pipe
|
|
global _restore_mode_child_installed
|
|
|
|
if _restore_mode_child_installed:
|
|
return
|
|
|
|
# Parent communicates to child by sending "mode packets" through a pipe:
|
|
mode_read_pipe, _mode_write_pipe = os.pipe()
|
|
|
|
if os.fork() == 0:
|
|
# Child process (watches for parent to die then restores video mode(s).
|
|
os.close(_mode_write_pipe)
|
|
|
|
# Set up SIGHUP to be the signal for when the parent dies.
|
|
PR_SET_PDEATHSIG = 1
|
|
libc = ctypes.cdll.LoadLibrary('libc.so.6')
|
|
libc.prctl.argtypes = (ctypes.c_int, ctypes.c_ulong, ctypes.c_ulong,
|
|
ctypes.c_ulong, ctypes.c_ulong)
|
|
libc.prctl(PR_SET_PDEATHSIG, signal.SIGHUP, 0, 0, 0)
|
|
|
|
# SIGHUP indicates the parent has died. The child lock is unlocked, it
|
|
# stops reading from the mode packet pipe and restores video modes on
|
|
# all displays/screens it knows about.
|
|
def _sighup(signum, frame):
|
|
parent_wait_lock.release()
|
|
|
|
parent_wait_lock = threading.Lock()
|
|
parent_wait_lock.acquire()
|
|
signal.signal(signal.SIGHUP, _sighup)
|
|
|
|
# Wait for parent to die and read packets from parent pipe
|
|
packets = []
|
|
buffer = asbytes('')
|
|
while parent_wait_lock.locked():
|
|
try:
|
|
data = os.read(mode_read_pipe, ModePacket.size)
|
|
buffer += data
|
|
# Decode packets
|
|
while len(buffer) >= ModePacket.size:
|
|
packet = ModePacket.decode(buffer[:ModePacket.size])
|
|
packets.append(packet)
|
|
buffer = buffer[ModePacket.size:]
|
|
except OSError:
|
|
pass # Interrupted system call
|
|
|
|
for packet in packets:
|
|
packet.set()
|
|
os._exit(0)
|
|
|
|
else:
|
|
# Parent process. Clean up pipe then continue running program as
|
|
# normal. Send mode packets through pipe as additional
|
|
# displays/screens are mode switched.
|
|
os.close(mode_read_pipe)
|
|
_restore_mode_child_installed = True
|
|
|
|
|
|
def set_initial_mode(mode):
|
|
_install_restore_mode_child()
|
|
|
|
display = xlib.XDisplayString(mode.screen.display._display)
|
|
screen = mode.screen.display.x_screen
|
|
|
|
# Only store one mode per screen.
|
|
if (display, screen) in _restorable_screens:
|
|
return
|
|
|
|
packet = ModePacket(display, screen, mode.width, mode.height, mode.rate)
|
|
|
|
os.write(_mode_write_pipe, packet.encode())
|
|
_restorable_screens.add((display, screen))
|