0
\$\begingroup\$

I'm having an issue that causes the window bar to appear above the screen when jumping between full-screen and windowed mode. Did some googling on the issue and it turns out it's actually a pygame bug, here

So I implemented the fix, and ended up hitting up against another issue where I get a segmentation fault from pygame.

When I delete this line pygame.display.quit() the segmentation fault goes away, but so does the workaround. So it's not really a fix.

Error 1

Error depends on whether I use pygame.quit() or the display quit above.

Fatal Python error: pygame_parachute: (pygame parachute) Segmentation Fault Python runtime state: initialized

I ended up with even a different error when I tried to create a little example program for this question

Error 2

self.txt_surface = self.FONT.render(" ", True, self.color) pygame.error:

Text has zero width

But let's focus on the first one. I enabled faulthandler on python which has helped me narrow down what line is causing the issue, and I narrowed it down to these few lines. After following back the error I found a problem with my textBox class, further found these offending lines.

def draw(self, screen):
    # Blit the text
    self.__hitkey__()
    roundedEdgeNum = 5
    pygame.draw.rect(screen, self.backgroundColor, self.rectBackground, 0,roundedEdgeNum)
    screen.blit(self.txt_surface, (self.rect.x+2, self.rect.y+2))

    pygame.draw.rect(screen, self.color, self.rect, 1,roundedEdgeNum)

What am I doing wrong with these draw and blit functions that causes an issue? I'm a little stumped on this one. Here is a full code example you can run to reproduce the issue. Thanks for any help! I really appreciate this community.

p.s. I know I'm mixing tkinter with a homegrown textbox, and I could just use a well tested GUI, but I spent a few days getting this text box to work the way I wanted it too, and so I want to use the code.

Code Reproducing Issue

import pygame
import tkinter
import os
import faulthandler
faulthandler.enable()

COLOR_INACTIVE = pygame.Color((128,128,128))
COLOR_ACTIVE = pygame.Color((0,0,0))
BACKGROUND_ACTIVE = (255,255,255)
BACKGROUND_INACTIVE = ((230,230,230))

class InputBox:

    def __init__(self, screen,x, y, w, h, text=''):
        self.screen = screen
        self.x = x
        self.y = y
        self.w = w
        self.h = h
        self.LIGHT_GREY = (180,180,180)
        self.textBackground = (255,255,255)
        self.outershadowWidth = 2
        self.outershadowHeight = 2
        self.shadowWidth = 2
        self.shadowHeight = 2
        self.xoffset = 1
        self.yoffset = 1
        self.shadowRect = pygame.Rect(x+self.xoffset ,y+self.yoffset ,w-self.shadowWidth,h-self.shadowHeight)
        self.shadowRectOuter = pygame.Rect(x-self.xoffset ,y-self.yoffset ,w+self.shadowWidth,h+self.shadowHeight)
        self.rect = pygame.Rect(x, y, w, h)
        self.rectBackground = pygame.Rect(x+1,y+1,w-1,h-1)
        self.color = COLOR_INACTIVE
        self.backgroundColor = BACKGROUND_INACTIVE
        self.text = text
        self.textCopy = ''
        self.FONT = pygame.font.SysFont("cambriacambriamath", 16)
        self.txt_surface = self.FONT.render(text, True, self.color)
        self.active = False
        self.font_size = 18
        self.cursor_color=(0, 0, 1)
        self.cursor_surface = pygame.Surface((int(self.font_size / 40 + 1), self.font_size-2))
        self.cursor_surface.fill(self.cursor_color)
        self.textBackgroundSurface = pygame.Surface((self.w, self.h))
        self.cursor_position    = len("")  # Inside text
        self.cursor_visible     = True  # Switches every self.cursor_switch_ms ms
        self.cursor_switch_ms   = 500
        self.cursor_ms_counter  = 0
        self.max_string_length  = 100
        self.keydown            = False
        self.backspaceDelay     = 100
        self.timer              = 0
        self.keyTimer           = 0
        self.backspaceTimer     = 0
        self.clock = pygame.time.Clock()
        self.wipeTextOnReturn = False
        self.countWipe        = 0


        self.screen_resolution = self.getScreenResolution()
        self.nonFullScreenSize=(int(self.screen_resolution[0]/2.0),int(self.screen_resolution[1]/2.0))
        self.fullscreen = True
        self.setScreenResolution()

    def handle_event(self,events):
        for event in events:
            if event.type == pygame.MOUSEBUTTONDOWN:
                # If the user clicked on the input_box rect.
                if self.rect.collidepoint(event.pos):
                    # Toggle the active variable.
                    self.active = not self.active
                else:
                    self.active = False
                # Change the current color of the input box.
                self.color = COLOR_ACTIVE if self.active else COLOR_INACTIVE
                self.backgroundColor = BACKGROUND_ACTIVE if self.active else BACKGROUND_INACTIVE
            if event.type == pygame.KEYDOWN:
                if self.active:
                    if event.key == pygame.K_RETURN:
                        self.textCopy = self.text
                        self.text = ''
                    else:
                        if ((len(self.text) < self.max_string_length) or (self.max_string_length == -1)):
                            if (event.unicode.isnumeric() or event.unicode == 'x'):
                                # If no special key is pressed, add unicode of key to text
                                self.text = self.text[:self.cursor_position] + event.unicode + self.text[self.cursor_position:]
                                self.cursor_position = len(self.text)  # Some are empty, e.g. K_UP
                    if (event.key == pygame.K_BACKSPACE):
                        self.text = self.text[:-1]
                        # Subtract one from cursor_pos, but do not go below zero:
                        self.cursor_position = max(self.cursor_position - 1, 0)
                        self.keydown         = True
                    if (event.key == pygame.K_LEFT):
                        if (self.cursor_position > 0):
                            self.cursor_position = self.cursor_position - 1
                    if (event.key == pygame.K_RIGHT):
                        # Add one to cursor_pos, but do not exceed len(text)
                        if (self.cursor_position < len(self.text)):
                            self.cursor_position = self.cursor_position + 1

            if event.type == pygame.KEYUP:
                self.keydown          = False
                self.backspaceTimer   = 0
                self.timer            = 0
                self.keyTimer         = 0

        #Handles backspace helddown, alternative to putting extra events on queue or using set_repeat
        #Putting set_repeat(1,30) in a program using this will mess up the event queue
        if (self.keydown == True):
            self.backspaceTimer += self.clock.get_time()
            self.timer += 1
            if (self.backspaceTimer >= self.backspaceDelay and (self.timer == 10)):
                self.text = self.text[:-1]
                #Subtract one from cursor_pos, but do not go below zero:
                self.cursor_position = max(self.cursor_position - 1, 0)
                self.timer = 0

        #Get key states
        key_states = pygame.key.get_pressed()
            
        if (key_states[pygame.K_END]):
            self.cursor_position = len(self.text)

        if (key_states[pygame.K_HOME]):
            self.cursor_position = 0

        # Re-render the text.
        if (self.active and self.text == ""):
            self.txt_surface = self.FONT.render(" ", True, self.color)
        else: 
            self.txt_surface = self.FONT.render(self.text, True, self.color)

        # Update self.cursor_visible
        self.cursor_ms_counter += self.clock.get_time()
        if (self.cursor_ms_counter >= self.cursor_switch_ms):
            self.cursor_ms_counter %= self.cursor_switch_ms
            self.cursor_visible = not self.cursor_visible

        if self.cursor_visible:
            cursor_y_pos = self.FONT.size(self.text[:self.cursor_position])[0]
            # Without this, the cursor is invisible when self.cursor_position > 0:
            if (self.cursor_position > 0):
                if (cursor_y_pos > 0):
                    cursor_y_pos -= self.cursor_surface.get_width()
            self.txt_surface.blit(self.cursor_surface, (cursor_y_pos, 2))
        
        self.update()

        self.draw(self.screen)

        self.clock.tick()

    def update(self):
        # Resize the box if the text is too long.
        width = max(200, self.txt_surface.get_width()+10)
        self.rect.w            = width
        self.shadowRect.w      = width-self.shadowWidth
        self.shadowRectOuter.w = width+self.shadowWidth
        self.rectBackground.w  = width-self.shadowWidth

    def draw(self, screen):
        # Blit the text
        self.__hitkey__()
        roundedEdgeNum = 5
        pygame.draw.rect(screen, self.backgroundColor, self.rectBackground, 0,roundedEdgeNum)
        screen.blit(self.txt_surface, (self.rect.x+2, self.rect.y+2))

        pygame.draw.rect(screen, self.color, self.rect, 1,roundedEdgeNum)



    def setScreenResolution(self):

        self.fullscreen = not self.fullscreen

        if (self.fullscreen):
            self.screen = pygame.display.set_mode(self.screen_resolution, pygame.FULLSCREEN,0)
        else:
            pygame.quit() # ???
            pygame.init() # ???
            os.environ['SDL_VIDEO_WINDOW_POS'] = str(100)+ "," + str(100)
            #os.environ['SDL_VIDEO_CENTERED'] = '1'
            
            self.screen = pygame.display.set_mode(self.nonFullScreenSize,pygame.RESIZABLE,0)

        pygame.display.update()

    def __hitkey__(self):
        '''Handle key events '''
        key_states = pygame.key.get_pressed()
        if (key_states[pygame.K_F3]):
            if (self.keyStateCounter == 0):
                self.setScreenResolution()
            self.keyStateCounter += 1
        if (not key_states[pygame.K_F3]):
            self.keyStateCounter = 0

    def getScreenResolution(self):
        """
        Workaround to get the size of the current screen in a multi-screen setup.

        Returns:
            geometry (str): The standard tkinter geometry string.
                [width]x[height]+[left]+[top]
        """
        root = tkinter.Tk()
        root.update_idletasks()
        root.attributes('-fullscreen', True)
        root.state('iconic')
        screen_width = root.winfo_screenwidth()
        screen_height = root.winfo_screenheight()
        root.destroy()

        return (screen_width,screen_height)


def main():
    screen = pygame.display.set_mode((640, 480))
    pygame.init()
    input_box1 = InputBox(screen,100, 100, 140, 32)

    done = False

    while not done:
        events = pygame.event.get()
        for event in events:
            if event.type == pygame.QUIT:
                done = True

        stuff = input_box1.handle_event(events)

        pygame.display.flip()



if __name__ == '__main__':
    main()
    pygame.quit()
\$\endgroup\$
1
\$\begingroup\$

The answer lies in this line here stuff = input_box1.handle_event(events)

The screen that was initially passed to handle_events, but once pygame.quit() is called the screen variable in input_box1 was not updated.

Changing the function to pass in the new screen surface fixed the issue.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.