I'm on Windows, where the curses module is not native, so I am using the Windows curses module for python 3.2, found here.
My goal is to resize the terminal, which is currently at a small 25 lines x 80 columns size. First I tried the curses.resizeterm(lines, cols) command, which is apparently not found in the windows curses module (and hasattr(curses, 'resizeterm') returned false). So I look at the alternative module unicurses, which is also for windows, but that doesn't even have a resize command.
So I do more reading and learn about the environment variables 'LINES' and 'COLS' which, when set by os.environ, should resize the terminal. And they do, kind of. The terminal itself gets resized, but the Windows program displaying the terminal is still the same size as before, 25 x 80. I have confirmed that the two variables have indeed been changed, writing a little thing to display them in the top left corner. In addition, the box() function does draw a border around the screen as if it the variables were changed.
So, can anyone explain either 1) how to resize the "Windows window" to match the terminal or 2) how to get resizeterm() to work on my python installation? The relevant code of my program and a picture of how it looks are attached below.
import random, sys, math, curses, os
from curses import *
curses.use_env(True)
os.environ['LINES'] = "80"
os.environ['COLS'] = "60"
stdscr = curses.initscr()
curses.noecho()
curses.cbreak()
curses.start_color()
stdscr.keypad(1)
curses.curs_set(0)
LINES, COLS = stdscr.getmaxyx()
This code might help:
import curses
screen = curses.initscr()
# Check if screen was re-sized (True or False)
resize = curses.is_term_resized(y, x)
# Action in loop if resize is True:
if resize is True:
y, x = screen.getmaxyx()
screen.clear()
curses.resizeterm(y, x)
screen.refresh()
Related
I tried searching everywhere on the internet, but nowhere did I find an answer that worked for me, so I'm asking a new question.
In my code, I'm telling curses to get the terminal size and update some variables according to it. On my first try, I did something like this (this is the function that gets called after the window has been resized manually):
def resize():
rows, cols = stdscr.getmaxyx()
#some more code irrelevant to the question
When I found out that doesn't work, I realised that the stdscr is probably not getting resized, even though the terminal is. So I tried closing the stdscr and initialising it again:
def resize():
curses.endwin()
curses.wrapper(initialise)
#some more code that's irrelevant to the question
#showing the initialise function just in case:
def initialise(stdscrarg):
global stdscr
stdscr = stdscrarg
That, however, also didn't work. In other words, the stdscr initialised in the size that the terminal had before resizing. i.e. if I had a terminal over a quarter of my screen and then resized it to full screen, the function would correctly deal with the warping of the text, but it would redraw the content adjusted to just the original quarter of the screen, and would leave the junk characters in the rest of the screen, like this:
Before resizing
After resizing, before refreshing
After refreshing.
As you can see, the stdscr has initialised to its original size instead of taking on the size of the terminal. And I have tested it and confirmed that it really is the size of the stdscr, and not just the text printed on it (as when I gradually add strings to it, it throws an error when it reaches the end of the original size screen, instead of continuing to the end of the terminal screen and only then throwing an error.)
My question is: How can I get the new size of the terminal and make stdscr initialise with this new size?
Working on Windows 11, Python 3.10, module curses (though when installing it through pip, its name is windows-curses)
I'm running a Debian 10 stable x64 system with the dwm window manager, and I'm using Python 3.7.3. From what I can tell from some example code and the draw_text method itself, I should be able to draw text on the root window with code like this:
#!/usr/bin/env python3
import Xlib
import Xlib.display
display = Xlib.display.Display()
screen = display.screen()
root = screen.root
gc = root.create_gc(foreground = screen.white_pixel, background = screen.black_pixel)
root.draw_text(gc, 1, 1, b"Hello, world!")
This code runs without error, but no text is displayed. I've also experimented with different coordinates without any luck. My root window background is just the default black, so I don't think the text is failing to show up, since I set the foreground pixel color to white.
You are right that your code should draw text on root window. You just need to:
Ensure that your background is indeed the root window (xwininfo is great)
Check the coordinates again: (i) if dwm, as by default, shows a topbar, it may hide the text. Or just [Alt]+b, to toggle the bar (ii) if you have other windows, for example your terminal, on top, you will not see the text.
Perform an XFlush in the end. Without it the request stays in the client.
The code that works here(Gentoo amd64/desktop/stable, dwm-6.2, python-3.6.9):
#!/usr/bin/env python3
import Xlib
import Xlib.display
display = Xlib.display.Display()
screen = display.screen()
root = screen.root
gc = root.create_gc(foreground = screen.white_pixel, background = screen.black_pixel)
root.draw_text(gc, 100, 100, b"Hello, world!") # changed the coords more towards the center
display.flush() # To actually send the request to the server
Notice that the text will disappear, if other windows overlap or refresh that spot. The text remains until, for example, you move a window over (erases it), or you change to another dwm-Tab that has a window covering these coordinates.
If you want to prevent the text from disappearing, you need a loop:
either a while True loop on the code as is, which is going to redraw it no matter what
or, better, an event loop, which is going to redraw it only when it is necessary (see below)
The expose events (refer https://tronche.com/gui/x/xlib/events/exposure/expose.html and http://python-xlib.sourceforge.net/doc/html/python-xlib_13.html#SEC12)
are generated when regions of a window has to be redrawn
BUT, if we listen for the expose event for root window, we get none (reason: (see the setup function in the dwm's source code) no ExposureMask for root). What i tried and worked:
#!/usr/bin/env python3
import Xlib
from Xlib import display, X # X is also needed
display = Xlib.display.Display()
screen = display.screen()
root = screen.root
#print(root.get_attributes())
root.change_attributes(event_mask=X.ExposureMask) # "adds" this event mask
#print(root.get_attributes()) # see the difference
gc = root.create_gc(foreground = screen.white_pixel, background = screen.black_pixel)
def draw_it():
root.draw_text(gc, 100, 100, b"Hello, world!")
display.flush()
draw_it()
while 1:
if display.pending_events() != 0: # check to safely apply next_event
event = display.next_event()
if event.type == X.Expose and event.count == 0:
draw_it()
I am using pygame to program a simple behavioral test. I'm running it on my macbook pro and have almost all the functionality working. However, during testing I'll have a second, external monitor that the subject sees and the laptop monitor. I'd like to have the game so up fullscreen on the external monitor and not on the laptop's monitor so that I can monitor performance. Currently, the start of the file looks something like:
#! /usr/bin/env python2.6
import pygame
import sys
stdscr = curses.initscr()
pygame.init()
screen = pygame.display.set_mode((1900, 1100), pygame.RESIZABLE)
I was thinking of starting the game in a resizable screen, but that OS X has problems resizing the window.
Pygame doesn't support two displays in a single pygame process(yet). See the question here and developer answer immediately after, where he says
Once SDL 1.3 is finished then pygame will get support for using multiple windows in the same process.
So, your options are:
Use multiple processes. Two pygame instances, each maximized on its own screen, communicating back and forth (you could use any of: the very cool python multiprocessing module, local TCP, pipes, writing/reading files, etc)
Set the same resolution on both of your displays, and create a large (wide) window that spans them with your information on one half and the user display on the other. Then manually place the window so that the user side is on their screen and yours is on the laptop screen. It's hacky, but might a better use of your time than engineering a better solution ("If it's studpid and it works, it ain't stupid" ;).
Use pyglet, which is similar to pygame and supports full screen windows: pyglet.window.Window(fullscreen=True, screens[1])
Good luck.
I do not know if you can do this in OS X, but this is worth mentioning for the Windows users out there, if you just want to have your program to run full screen on the second screen and you are on windows, just set the other screen as the main one.
The setting can be found under Rearrange Your Displays in settings.
So far for me anything that I can run on my main display can run this way, no need to change your code.
I did something silly but it works.
i get the number of monitors with get_monitors()
than i use SDL to change the pygame window's display position by adding to it the width of the smallest screen, to be sure that the window will be positionned in the second monitor.
from screeninfo import get_monitors
numberOfmonitors = 0
smallScreenWidth = 9999
for monitor in get_monitors():
#getting the smallest screen width
smallScreenWidth = min(smallScreenWidth, monitor.width)
numberOfmonitors += 1
if numberOfmonitors > 1:
x = smallScreenWidth
y = 0
#this will position the pygame window in the second monitor
os.environ['SDL_VIDEO_WINDOW_POS'] = "%d,%d" % (x,y)
#you can check with a small window
#screen = pygame.display.set_mode((100,100))
#or go full screen in second monitor
screen = pygame.display.set_mode((0, 0), pygame.FULLSCREEN)
#if you want to do other tasks on the laptop (first monitor) while the pygame window is being displayed on the second monitor, you shoudn't use fullscreen but instead get the second monitor's width and heigh using monitor.width and monitor.height, and set the display mode like
screen = pygame.display.set_mode((width,height))
display = pyglet.canvas.get_display()
display = display.get_screens()
win = pyglet.window.Window(screen=display[1])
------------------------------------------------------
screen=display[Номер монитора]
------------------------------------------------------
display = pyglet.canvas.get_display()
display = display.get_screens()
print(display) # Все мониторы которые есть
This is two questions really:
how do I resize a curses window, and
how do I deal with a terminal resize in curses?
Is it possible to know when a window has changed size?
I really can't find any good doc, not even covered on http://docs.python.org/library/curses.html
Terminal resize event will result in the curses.KEY_RESIZE key code. Therefore you can handle terminal resize as part of a standard main loop in a curses program, waiting for input with getch.
I got my python program to re-size the terminal by doing a couple of things.
# Initialize the screen
import curses
screen = curses.initscr()
# Check if screen was re-sized (True or False)
resize = curses.is_term_resized(y, x)
# Action in loop if resize is True:
if resize is True:
y, x = screen.getmaxyx()
screen.clear()
curses.resizeterm(y, x)
screen.refresh()
As I'm writing my program I can see the usefulness of putting my screen into it's own class with all of these functions defined so all I have to do is call Screen.resize() and it would take care of the rest.
I use the code from here.
In my curses-script I don't use getch(), so I can't react to KEY_RESIZE.
Therefore the script reacts to SIGWINCH and within the handler re-inits the curses library. That means of course, you'll have to redraw everything, but I could not find a better solution.
Some example code:
from curses import initscr, endwin
from signal import signal, SIGWINCH
from time import sleep
stdscr = initscr()
def redraw_stdscreen():
rows, cols = stdscr.getmaxyx()
stdscr.clear()
stdscr.border()
stdscr.hline(2, 1, '_', cols-2)
stdscr.refresh()
def resize_handler(signum, frame):
endwin() # This could lead to crashes according to below comment
stdscr.refresh()
redraw_stdscreen()
signal(SIGWINCH, resize_handler)
initscr()
try:
redraw_stdscreen()
while 1:
# print stuff with curses
sleep(1)
except (KeyboardInterrupt, SystemExit):
pass
except Exception as e:
pass
endwin()
This worked for me when using curses.wrapper():
if stdscr.getch() == curses.KEY_RESIZE:
curses.resizeterm(*stdscr.getmaxyx())
stdscr.clear()
stdscr.refresh()
It isn't right. It's an ncurses-only extension. The question asked about curses. To do this in a standards-conforming way you need to trap SIGWINCH yourself and arrange for the screen to be redrawn.
I can't seem to get white-on-black to work in curses when in color mode. If I don't call start_color, I get white-on-black. As soon as I call start_color, things start outputting in grey-on-black.
If you run this script:
import sys
for i in xrange(30, 38):
print '\x1b[0;' + str(i) + 'm' + str(i) + ': Shiny colors \x1b[1m(bright)'
print '\x1b[0m...and this is normal.'
...you'll probably see a lot of pretty colors. The one I want, and can't get, is the last line: '...and this is normal.' Asking for color pair 0 or asking for COLOR_WHITE, COLOR_BLACK gets me the non-bright #37 from the script.
For reference, this is what I see in Gnome Terminal:
http://rpi.edu/~wellir/random/colors.png
I'm programming in Python (using the curses library), so my code is something like:
import curses
screen = curses.initscr()
curses.start_color()
curses.init_pair(1, curses.COLOR_WHITE, curses.COLOR_BLACK)
screen.clear()
screen.attrset(0)
screen.addstr('Hello')
screen.attrset(curses.A_BOLD)
screen.addstr('Hello')
screen.attrset(curses.color_pair(1))
screen.addstr('Hello')
screen.refresh()
curses.napms(5000)
curses.endwin()
...which gets me 37, 37-bright, and 37.
curses.use_default_colors()
Your gnome terminal may have its own color scheme, which changes the colors of the default white to bright white, except when in curses mode. Check that gnome-terminal does not changes the colors, because this would make testing the colors difficult.
I was on gnome terminal too with the same problem.
I managed to solve it with:
right click on screen > profile > profile preferences > color > palette
I think this is what each of the 8 colors will map to.
for some reason, the built-in scheme Default that was selected mapped the first color to gray instead of black!
changing scheme to XTerm, or changing the first color to black solved my problem.
I am not using curses.use_default_colors.