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()