Close Menu
    DevStackTipsDevStackTips
    • Home
    • News & Updates
      1. Tech & Work
      2. View All

      From Data To Decisions: UX Strategies For Real-Time Dashboards

      September 13, 2025

      Honeycomb launches AI observability suite for developers

      September 13, 2025

      Low-Code vs No-Code Platforms for Node.js: What CTOs Must Know Before Investing

      September 12, 2025

      ServiceNow unveils Zurich AI platform

      September 12, 2025

      Building personal apps with open source and AI

      September 12, 2025

      What Can We Actually Do With corner-shape?

      September 12, 2025

      Craft, Clarity, and Care: The Story and Work of Mengchu Yao

      September 12, 2025

      Distribution Release: Q4OS 6.1

      September 12, 2025
    • Development
      1. Algorithms & Data Structures
      2. Artificial Intelligence
      3. Back-End Development
      4. Databases
      5. Front-End Development
      6. Libraries & Frameworks
      7. Machine Learning
      8. Security
      9. Software Engineering
      10. Tools & IDEs
      11. Web Design
      12. Web Development
      13. Web Security
      14. Programming Languages
        • PHP
        • JavaScript
      Featured

      Optimizely Mission Control – Part III

      September 14, 2025
      Recent

      Optimizely Mission Control – Part III

      September 14, 2025

      Learning from PHP Log to File Example

      September 13, 2025

      Online EMI Calculator using PHP – Calculate Loan EMI, Interest, and Amortization Schedule

      September 13, 2025
    • Operating Systems
      1. Windows
      2. Linux
      3. macOS
      Featured

      sudo vs sudo-rs: What You Need to Know About the Rust Takeover of Classic Sudo Command

      September 14, 2025
      Recent

      sudo vs sudo-rs: What You Need to Know About the Rust Takeover of Classic Sudo Command

      September 14, 2025

      Dmitry — The Deep Magic

      September 13, 2025

      Right way to record and share our Terminal sessions

      September 13, 2025
    • Learning Resources
      • Books
      • Cheatsheets
      • Tutorials & Guides
    Home»Development»How to Run Python GUI Apps in GitHub Codespaces with Xvfb and noVNC

    How to Run Python GUI Apps in GitHub Codespaces with Xvfb and noVNC

    September 14, 2025

    GitHub Codespaces gives you a full development environment in the cloud, directly in your browser. It’s great for writing and running code, but there’s one big limitation: it doesn’t support graphical applications out of the box, especially for Python code.

    If you try to run a Python GUI library like Pygame, Tkinter, or PyQt inside Codespaces, you’ll get an error. That’s because Codespaces runs in a headless environment. There’s no physical display for your app to open a window on.

    In this article, I’ll show you how to fix that. You’ll learn how to set up a virtual desktop using Xvfb and stream it into your browser using noVNC. By the end, you’ll be able to run any Python GUI application inside GitHub Codespaces.

    Table of Contents

    • Why Codespaces Needs Extra Setup for GUIs

    • Step 1: Create the Repo and Open Codespace

    • Step 2: Add the Setup Script

    • Step 3: Start the GUI Environment

    • Step 4: Open the noVNC Desktop

    • Step 5: Run Your Python GUI App

    • Tips

    • Conclusion

    Prerequisites

    Before you start, you should have:

    • A GitHub account and access to GitHub Codespaces.

    • Basic familiarity with Python.

    • A Python GUI app to test (we’ll use a small Pygame example).

    Why Codespaces Needs Extra Setup for GUIs

    When you run GUI code like:

    <span class="hljs-keyword">import</span> pygame
    pygame.display.set_mode((<span class="hljs-number">800</span>, <span class="hljs-number">600</span>))
    

    On your local machine, Python tells your operating system to create a window. But Codespaces runs on a server with no monitor attached. Without a display, your GUI app cannot render.

    That’s where Xvfb (X virtual framebuffer) comes in. It simulates a display in memory, so GUI programs think they’re running on a real screen. To make that screen visible in the browser, you can use noVNC, which streams the virtual display through a web client.

    Together, Xvfb and noVNC turn Codespaces into a cloud-based desktop for GUI apps.

    Step 1: Create the Repo and Open Codespace

    First, create a GitHub repository for your project (or a demo repo) and open it in Codespaces:

    Screenshot of a new repository

    Step 2: Add the Setup Script

    Create a file called start-gui.sh in the root of your project.

    a screenshot of GitHub codespace with a bash file created

    Paste the following code into the start-gui.sh file:

    <span class="hljs-meta">#!/usr/bin/env bash</span>
    <span class="hljs-built_in">set</span> -e
    
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Installing dependencies..."</span>
    sudo apt-get update -y
    sudo apt-get install -y xvfb x11vnc fluxbox websockify novnc
    
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting virtual display..."</span>
    Xvfb :1 -screen 0 1024x768x24 &
    <span class="hljs-built_in">export</span> DISPLAY=:1
    fluxbox &
    
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting VNC server..."</span>
    x11vnc -display :1 -nopw -forever -shared -rfbport 5900 &
    
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Starting noVNC on port 6080..."</span>
    websockify --web=/usr/share/novnc 6080 localhost:5900 &
    
    <span class="hljs-built_in">echo</span> <span class="hljs-string">""</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"GUI environment is ready!"</span>
    <span class="hljs-built_in">echo</span> <span class="hljs-string">"Go to the Ports tab, set port 6080 to Public, and open the link."</span>
    

    Let’s explain this script so you can understand what it does:

    set -e

    • This tells the shell to exit immediately if any command fails.

    • Without it, the script would keep running even if something goes wrong (like a failed install).

    Installing Dependencies

    sudo apt-get update -y: updates your package list.

    sudo apt-get install -y ⁣installs the packages we listed (xvfb, x11vnc, fluxbox, websockify, and novnc)

      • xvfb: creates a “dummy” display (virtual screen in memory).

        • x11vnc: shares that dummy display via the VNC protocol.

        • Fluxbox: a lightweight window manager, so the desktop has a GUI environment.

        • websockify: converts VNC traffic into WebSockets so it can run in a browser.

        • novnc: provides a browser client to connect to the desktop.

    Virtual Display

    • Xvfb :1 -screen 0 1024x768x24 &: Starts the virtual framebuffer on display :1 with resolution 1024x768 and 24-bit color.

    • export DISPLAY=:1: tells apps (like Python GUIs) to draw on this virtual screen instead of looking for a real display unit.

    • fluxbox &: launches the window manager so GUI apps have a desktop to sit in.

    VNC Server

    • x11vnc -display :1: connects you to the dummy display (:1).

    • -nopw: ensures that no password is required.

    • -forever: this keeps the VNC running even if clients disconnect.

    • -shared: allows multiple clients.

    • -rfbport 5900: exposes the internal server on VNC’s standard port.

    noVNC Server

    • websockify acts as a bridge that converts WebSocket traffic to VNC protocol (on port 5900).

    • --web=/usr/share/novnc: serves the noVNC web client files.

    • 6080: the port where you’ll connect in your browser (this is publicly accessible).

    • localhost:5900:forwards traffic to the VNC server that was started earlier.

    You should only expose port 6080 (noVNC) as Public and keep 5900 (raw VNC) private because:

    • Prt 5900 (VNC) uses the raw VNC protocol, which is not encrypted and doesn’t require a password in this setup. If exposed, anyone could connect directly and control your Codespace desktop.

    • Prt 6080 (noVNC) runs over WebSockets + HTTPS, so traffic is encrypted and secured through GitHub Codespaces’ connection. It also only serves the noVNC web client, not the raw VNC protocol.

    5900 = unsafe to expose, 6080 = browser-safe way to view the GUI.

    The next step is for you to make the bash file executable by running the code below in the terminal:

    chmod +x start-gui.sh
    

    Step 3: Start the GUI Environment

    Run the script:

    ./start-gui.sh
    

    the terminal while running the bash script

    This will:

    1. Install all dependencies (Xvfb, fluxbox, x11vnc, novnc).

    2. Start a virtual display (DISPLAY=:1).

    3. Launch a lightweight window manager (fluxbox).

    4. Stream the desktop to your browser via noVNC on port 6080.

    Step 4: Open the noVNC Desktop

    1. In Codespaces, open the Ports tab.

    2. Find port 6080 and change its visibility to public. (right-click on the private word)

    3. Open the URL in a new browser tab.

    4. Click vnc.html or vnc_auto.html if prompted.

      screenshot of the port tab on codespace

    open the port 6080 forwared address in a new tab, and you'd get this screen

    You should now see a lightweight Linux desktop running inside your browser.

    a light weight linux desktop running on your browser

    Step 5: Run Your Python GUI App

    In a new Codespaces terminal, run:

    <span class="hljs-keyword">export</span> DISPLAY=:<span class="hljs-number">1</span>
    python3 your_script.py
    

    Your Python GUI app should appear inside the noVNC desktop 🎉.

    For example, here’s a simple Pygame script test.py:

    <span class="hljs-keyword">import</span> pygame
    <span class="hljs-keyword">from</span> pygame <span class="hljs-keyword">import</span> display, font, event
    <span class="hljs-keyword">from</span> pygame.locals <span class="hljs-keyword">import</span> *
    
    <span class="hljs-comment"># Setup display</span>
    pygame.init()
    screen = display.set_mode()
    display.set_caption(<span class="hljs-string">"Capstone 2"</span>)
    myFont = font.SysFont(<span class="hljs-string">'arial'</span>, <span class="hljs-number">12</span>)  <span class="hljs-comment"># Choose a font to use in game</span>
    
    <span class="hljs-comment"># Directions displayed throughout game</span>
    directions = <span class="hljs-string">"Please press the 'Y' key for yes and the 'N' key for no."</span>
    
    <span class="hljs-comment"># Counts how many questions have been asked</span>
    currentQuestion = <span class="hljs-number">0</span>
    
    
    <span class="hljs-comment"># Determines which question to ask</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">story</span>(<span class="hljs-params">answer, count</span>):</span>
        screen.fill(<span class="hljs-string">"white"</span>)
        <span class="hljs-keyword">if</span> count == <span class="hljs-number">0</span>:
            question1(answer)
        <span class="hljs-keyword">elif</span> count == <span class="hljs-number">1</span>:
            question2(answer)
        <span class="hljs-keyword">elif</span> count == <span class="hljs-number">2</span>:
            question3(answer)
        <span class="hljs-keyword">elif</span> count == <span class="hljs-number">3</span>:
            end(answer)
    
    
    <span class="hljs-comment"># Displays the first part of the story</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">intro</span>():</span>
        <span class="hljs-comment"># Break up the string into multiple variables because there isn't text wrapping in Pygame</span>
        intro1 = <span class="hljs-string">"Once upon a time lived a brave hero named Anya."</span>
        intro2 = <span class="hljs-string">"She lived a simple life in a small village, making biscuits for the village people."</span>
        intro3 = <span class="hljs-string">"One day, late at night, she hears a loud noise outside the village."</span>
        q1 = <span class="hljs-string">"Should she go outside to investigate? Yes or no?"</span>
    
        screen.fill(<span class="hljs-string">"white"</span>)
        textSurface = myFont.render(intro1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
        textSurface = myFont.render(intro2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(intro3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(q1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
    
    
    <span class="hljs-comment"># First question</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">question1</span>(<span class="hljs-params">answer</span>):</span>
        <span class="hljs-keyword">if</span> answer == K_y:
            yes1 = <span class="hljs-string">"She ventures into the dark, prepared for danger."</span>
            yes2 = <span class="hljs-string">"Eventually, she sees an army of ogres coming toward her village!"</span>
            q2 = <span class="hljs-string">"Should she fight the ogres? Yes or no?"</span>
    
            textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
            textSurface = myFont.render(yes2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
            textSurface = myFont.render(q2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
            textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
    
        <span class="hljs-keyword">elif</span> answer == K_n:
            no1 = <span class="hljs-string">"She chooses the safety of her home and stays inside."</span>
            no2 = <span class="hljs-string">"However, the sounds do not go away."</span>
            no3 = <span class="hljs-string">"She can tell something is very wrong..."</span>
            no4 = <span class="hljs-string">"Eventually, she sees an army of ogres coming toward her village!"</span>
            q2 = <span class="hljs-string">"Should she fight the ogres? Yes or no?"</span>
    
            textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
            textSurface = myFont.render(no2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
            textSurface = myFont.render(no3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
            textSurface = myFont.render(no4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
            textSurface = myFont.render(q2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
            textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))
    
    
    <span class="hljs-comment"># Second question</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">question2</span>(<span class="hljs-params">answer</span>):</span>
        <span class="hljs-keyword">if</span> answer == K_y:
            yes1 = <span class="hljs-string">"She bravely confronts the ogres, hoping to protect her village from harm."</span>
            textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
    
        <span class="hljs-keyword">elif</span> answer == K_n:
            no1 = <span class="hljs-string">"The ogres raid the village but Anya manages to escape with her life."</span>
            textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
    
        story2 = <span class="hljs-string">"The ogres decide to leave but she knows they will be back."</span>
        story3 = <span class="hljs-string">"Anya decides to talk with a village elder about what she should do."</span>
        story4 = <span class="hljs-string">"The elder says there is a powerful sword hidden in the Ancient Forest."</span>
        q3 = <span class="hljs-string">"Should Anya risk her life to retrieve it? Yes or no?"</span>
    
        textSurface = myFont.render(story2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
        textSurface = myFont.render(story3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
        textSurface = myFont.render(story4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
        textSurface = myFont.render(q3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
        textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
        screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))
    
    
    <span class="hljs-comment"># Third question</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">question3</span>(<span class="hljs-params">answer</span>):</span>
        <span class="hljs-keyword">if</span> answer == K_y:
            yes1 = <span class="hljs-string">"Although Anya almost died in the Ancient Forest,"</span>
            yes2 = <span class="hljs-string">"she returns with the Sword of Legends!"</span>
            yes3 = <span class="hljs-string">"In the dead of winter, the ogres come back."</span>
            yes4 = <span class="hljs-string">"This time they are being led by their evil king."</span>
            q4 = <span class="hljs-string">"Should Anya fight the ogre king now that she has the Sword of Legends?"</span>
    
            textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
            textSurface = myFont.render(yes2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
            textSurface = myFont.render(yes3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
            textSurface = myFont.render(yes4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
            textSurface = myFont.render(q4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
            textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))
    
        <span class="hljs-keyword">elif</span> answer == K_n:
            no1 = <span class="hljs-string">"Anya decides it's too risky to go into the forest alone."</span>
            no2 = <span class="hljs-string">"She hopes for the best with the weapons she has."</span>
            no3 = <span class="hljs-string">"In the dead of winter, the ogres come back."</span>
            no4 = <span class="hljs-string">"This time they are being led by their evil king."</span>
            q4 = <span class="hljs-string">"Should Anya fight the king even though she doesn't have the Sword of Legends?"</span>
    
            textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
            textSurface = myFont.render(no2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
            textSurface = myFont.render(no3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
            textSurface = myFont.render(no4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
            textSurface = myFont.render(q4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
            textSurface = myFont.render(directions, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))
    
    
    <span class="hljs-comment"># Ending</span>
    <span class="hljs-function"><span class="hljs-keyword">def</span> <span class="hljs-title">end</span>(<span class="hljs-params">answer</span>):</span>
        <span class="hljs-keyword">if</span> answer == K_y:
            yes1 = <span class="hljs-string">"Tension fills the air as she prepares to fight the king. The duel commences..."</span>
            end1 = <span class="hljs-string">"After an intense battle, Anya strikes the final blow!"</span>
            end2 = <span class="hljs-string">"The king surrenders and pleads for mercy."</span>
            end3 = <span class="hljs-string">"Anya is a true hero, who shows mercy to the king."</span>
            end4 = <span class="hljs-string">"This act of kindness warms the evil king's heart,"</span>
            end5 = <span class="hljs-string">"who promises to leave the village alone for eternity."</span>
            end6 = <span class="hljs-string">"The end!"</span>
    
            textSurface = myFont.render(yes1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
            textSurface = myFont.render(end1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
            textSurface = myFont.render(end2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
            textSurface = myFont.render(end3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
            textSurface = myFont.render(end4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
            textSurface = myFont.render(end5, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">80</span>))
            textSurface = myFont.render(end6, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">94</span>))
    
        <span class="hljs-keyword">elif</span> answer == K_n:
            no1 = <span class="hljs-string">"Anya refuses to duel the king, who laughs at her cowardice."</span>
            end1 = <span class="hljs-string">"This buys some time for the villagers to escape."</span>
            end2 = <span class="hljs-string">"Sadly, the ogre king takes over Anya's village."</span>
            end3 = <span class="hljs-string">"She is just thankful that the villagers were able to get to safety."</span>
            end4 = <span class="hljs-string">"The end!"</span>
    
            textSurface = myFont.render(no1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">10</span>))
            textSurface = myFont.render(end1, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">24</span>))
            textSurface = myFont.render(end2, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">38</span>))
            textSurface = myFont.render(end3, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">52</span>))
            textSurface = myFont.render(end4, <span class="hljs-literal">True</span>, <span class="hljs-string">"black"</span>)
            screen.blit(textSurface, (<span class="hljs-number">10</span>, <span class="hljs-number">66</span>))
    
    
    <span class="hljs-comment"># Game loop</span>
    <span class="hljs-keyword">while</span> <span class="hljs-literal">True</span>:
        <span class="hljs-comment"># Checks to see if at beginning of game</span>
        <span class="hljs-keyword">if</span> currentQuestion == <span class="hljs-number">0</span>:
            intro()
    
        <span class="hljs-comment"># Get the most recent event</span>
        currentEvent = event.poll()
    
        <span class="hljs-comment"># Displays the correct question based on event that occurs</span>
        <span class="hljs-keyword">if</span> currentEvent.type == KEYDOWN:
            story(currentEvent.key, currentQuestion)
            currentQuestion = currentQuestion + <span class="hljs-number">1</span>
    
        <span class="hljs-comment"># add text to screen</span>
        display.update()
    

    code source: codecombat (developing python game)

    The code above is a simple interactive story game written with Pygame. In the first few lines, you import Pygame and its display, font, and event modules.

    pygame.locals brings in constants like K_y (Y key), K_n (N key), and⁣ KEYDOWN.

    The preceding line then initializes Pygame and creates a window (screen). It also sets the window title and then loads a font for rendering the text.

    Then you have the section for functions that power the story (intro, question1, question2, question3, and conditions based on the player’s answer).

    In summary, the code is a choose-your-own-adventure text game with a single character, Anya. The choices of the player determine which text is shown.

    To run this script, install pygame.

    sudo apt-get update
    sudo apt-get install -y python3-pygame
    pip install pygame
    

    The above code will install pygame into your environment, after which you can then run the script.

    python3 test.py
    

    When you run this inside Codespaces, the window will appear in the noVNC tab. If it doesn’t open automatically, click on connect.

    screenshot of the python gui app output

    Tips

    ALSA error in codespaces

    • Ignore ALSA errors: Codespaces doesn’t have sound output, so audio warnings are normal.

    • Adjust resolution: Change 1024x768x24 in the script if you want a bigger (or smaller) screen.

    • Use with other libraries: Tkinter, PyQt, and Matplotlib interactive plots. All will work with this setup.

    • Automate DISPLAY export: Add export DISPLAY=:1 in your bash file if you don’t want to type it each time.

    Conclusion

    You’ve just turned GitHub Codespaces into a Python GUI environment. By using Xvfb and noVNC, you can run apps that normally require a desktop environment right inside your browser.

    Whether you’re building games, testing interfaces, or teaching Python graphics, you can now do it all in Codespaces without leaving the cloud.

    Want to try it yourself? Clone this repo, run ./start-gui.sh, and launch your first GUI app in Codespaces today.

    Source: freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More 

    Facebook Twitter Reddit Email Copy Link
    Previous ArticleHow to Work with Collections in Go Using the Standard Library Helpers
    Next Article New AI system uncovers hidden cell subtypes, boosts precision medicine

    Related Posts

    Repurposing Protein Folding Models for Generation with Latent Diffusion
    Artificial Intelligence

    Repurposing Protein Folding Models for Generation with Latent Diffusion

    September 14, 2025
    Artificial Intelligence

    Scaling Up Reinforcement Learning for Traffic Smoothing: A 100-AV Highway Deployment

    September 14, 2025
    Leave A Reply Cancel Reply

    For security, use of Google's reCAPTCHA service is required which is subject to the Google Privacy Policy and Terms of Use.

    Continue Reading

    CVE-2025-24343 – CtrlX OS File Write Vulnerability

    Common Vulnerabilities and Exposures (CVEs)

    CVE-2025-49735 – “Microsoft Windows KPSSVC Use-After-Free Code Execution Vulnerability”

    Common Vulnerabilities and Exposures (CVEs)

    Nintendo Raises Prices for Switch Consoles, Accessories & More

    Operating Systems

    How AI is Changing the Way We Code

    Development

    Highlights

    CVE-2025-4526 – Dígitro NGC Explorer Password Field Masking Vulnerability

    May 11, 2025

    CVE ID : CVE-2025-4526

    Published : May 11, 2025, 1:15 a.m. | 2 hours, 24 minutes ago

    Description : A vulnerability, which was classified as problematic, was found in Dígitro NGC Explorer 3.44.15. This affects an unknown part of the component Configuration Page. The manipulation leads to missing password field masking. It is possible to initiate the attack remotely. The vendor was contacted early about this disclosure but did not respond in any way.

    Severity: 4.3 | MEDIUM

    Visit the link for more details, such as CVSS details, affected products, timeline, and more…

    This one Elden Ring Nightreign feature saved the day when I needed it most

    June 11, 2025

    FOSS Weekly #25.27: System Info, Retro Tools, Fedora 32-bit Update, Torvalds vs Bcachefs and More Linux Stuff

    July 3, 2025

    CVE-2025-24000 – WPExperts Post SMTP Authentication Bypass

    August 7, 2025
    © DevStackTips 2025. All rights reserved.
    • Contact
    • Privacy Policy

    Type above and press Enter to search. Press Esc to cancel.