glances_curses.py 27.3 KB
Newer Older
1 2
# -*- coding: utf-8 -*-
#
3
# This file is part of Glances.
4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
#
# Copyright (C) 2014 Nicolargo <nicolas@nicolargo.com>
#
# Glances is free software; you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Glances is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
#
# You should have received a copy of the GNU Lesser General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.

A
PEP 257  
Alessio Sergi 已提交
20 21
"""Curses interface class."""

22 23 24 25
# Import system lib
import sys

# Import Glances lib
26
from glances.core.glances_globals import glances_logs, glances_processes, is_linux, is_bsd, is_mac, is_windows, logger
A
Alessio Sergi 已提交
27
from glances.core.glances_timer import Timer
28 29

# Import curses lib for "normal" operating system and consolelog for Windows
30
if not is_windows:
31 32 33
    try:
        import curses
        import curses.panel
N
Nicolargo 已提交
34
        from curses.textpad import Textbox, rectangle
35
    except ImportError:
N
Nicolas Hennion 已提交
36
        logger.critical('Curses module not found. Glances cannot start in standalone mode.')
37 38
        sys.exit(1)
else:
39 40
    from glances.outputs.glances_colorconsole import WCurseLight
    curses = WCurseLight()
41 42


A
Alessio Sergi 已提交
43
class GlancesCurses(object):
44

A
PEP 257  
Alessio Sergi 已提交
45
    """This class manages the curses display (and key pressed)."""
46

A
PEP 257  
Alessio Sergi 已提交
47
    def __init__(self, args=None):
48 49
        # Init args
        self.args = args
N
Nicolas Hennion 已提交
50

51 52 53 54 55 56 57 58 59 60 61
        # Init windows positions
        self.term_w = 80
        self.term_h = 24

        # Space between stats
        self.space_between_column = 3
        self.space_between_line = 2

        # Init the curses screen
        self.screen = curses.initscr()
        if not self.screen:
N
Nicolas Hennion 已提交
62
            logger.critical(_("Error: Cannot init the curses library.\n"))
N
Nicolas Hennion 已提交
63
            sys.exit(1)
64 65 66 67 68 69 70 71 72 73

        # Set curses options
        if hasattr(curses, 'start_color'):
            curses.start_color()
        if hasattr(curses, 'use_default_colors'):
            curses.use_default_colors()
        if hasattr(curses, 'noecho'):
            curses.noecho()
        if hasattr(curses, 'cbreak'):
            curses.cbreak()
N
Nicolargo 已提交
74
        self.set_cursor(0)
75 76 77 78 79 80

        # Init colors
        self.hascolors = False
        if curses.has_colors() and curses.COLOR_PAIRS > 8:
            self.hascolors = True
            # FG color, BG color
N
Nicolargo 已提交
81 82 83 84
            if args.theme_white:
                curses.init_pair(1, curses.COLOR_BLACK, -1)
            else:
                curses.init_pair(1, curses.COLOR_WHITE, -1)
85 86 87 88 89 90 91 92 93 94 95
            curses.init_pair(2, curses.COLOR_WHITE, curses.COLOR_RED)
            curses.init_pair(3, curses.COLOR_WHITE, curses.COLOR_GREEN)
            curses.init_pair(4, curses.COLOR_WHITE, curses.COLOR_BLUE)
            curses.init_pair(5, curses.COLOR_WHITE, curses.COLOR_MAGENTA)
            curses.init_pair(6, curses.COLOR_RED, -1)
            curses.init_pair(7, curses.COLOR_GREEN, -1)
            curses.init_pair(8, curses.COLOR_BLUE, -1)
            curses.init_pair(9, curses.COLOR_MAGENTA, -1)
        else:
            self.hascolors = False

A
Alessio Sergi 已提交
96
        if args.disable_bold:
97 98 99 100 101 102 103 104 105 106
            A_BOLD = curses.A_BOLD
        else:
            A_BOLD = 0

        self.title_color = A_BOLD
        self.title_underline_color = A_BOLD | curses.A_UNDERLINE
        self.help_color = A_BOLD
        if self.hascolors:
            # Colors text styles
            self.no_color = curses.color_pair(1)
A
Alessio Sergi 已提交
107
            self.default_color = curses.color_pair(3) | A_BOLD
108
            self.nice_color = curses.color_pair(9) | A_BOLD
109 110 111 112 113 114 115 116 117 118 119
            self.ifCAREFUL_color = curses.color_pair(4) | A_BOLD
            self.ifWARNING_color = curses.color_pair(5) | A_BOLD
            self.ifCRITICAL_color = curses.color_pair(2) | A_BOLD
            self.default_color2 = curses.color_pair(7) | A_BOLD
            self.ifCAREFUL_color2 = curses.color_pair(8) | A_BOLD
            self.ifWARNING_color2 = curses.color_pair(9) | A_BOLD
            self.ifCRITICAL_color2 = curses.color_pair(6) | A_BOLD
        else:
            # B&W text styles
            self.no_color = curses.A_NORMAL
            self.default_color = curses.A_NORMAL
120
            self.nice_color = A_BOLD
121 122 123 124 125 126 127 128 129 130 131 132 133
            self.ifCAREFUL_color = curses.A_UNDERLINE
            self.ifWARNING_color = A_BOLD
            self.ifCRITICAL_color = curses.A_REVERSE
            self.default_color2 = curses.A_NORMAL
            self.ifCAREFUL_color2 = curses.A_UNDERLINE
            self.ifWARNING_color2 = A_BOLD
            self.ifCRITICAL_color2 = curses.A_REVERSE

        # Define the colors list (hash table) for stats
        self.__colors_list = {
            'DEFAULT': self.no_color,
            'UNDERLINE': curses.A_UNDERLINE,
            'BOLD': A_BOLD,
134
            'SORT': A_BOLD,
135
            'OK': self.default_color2,
N
Nicolargo 已提交
136
            'FILTER': self.ifCAREFUL_color2,
137
            'TITLE': self.title_color,
138 139
            'PROCESS': self.default_color2,
            'STATUS': self.default_color2,
140
            'NICE': self.nice_color,
141 142 143 144 145 146 147 148 149 150 151 152 153
            'CAREFUL': self.ifCAREFUL_color2,
            'WARNING': self.ifWARNING_color2,
            'CRITICAL': self.ifCRITICAL_color2,
            'OK_LOG': self.default_color,
            'CAREFUL_LOG': self.ifCAREFUL_color,
            'WARNING_LOG': self.ifWARNING_color,
            'CRITICAL_LOG': self.ifCRITICAL_color
        }

        # Init main window
        self.term_window = self.screen.subwin(0, 0)

        # Init refresh time
N
Nicolargo 已提交
154
        self.__refresh_time = args.time
155 156 157 158

        # Init process sort method
        self.args.process_sorted_by = 'auto'

N
Nicolargo 已提交
159 160 161
        # Init edit filter tag
        self.edit_filter = False

162 163 164 165 166
        # Catch key pressed with non blocking mode
        self.term_window.keypad(1)
        self.term_window.nodelay(1)
        self.pressedkey = -1

167 168 169 170 171 172 173 174 175 176 177
        # History tag
        self.reset_history_tag = False
        self.history_tag = False
        if args.enable_history:
            logger.info('Stats history enabled')
            from glances.outputs.glances_history import GlancesHistory
            self.glances_history = GlancesHistory()
            if not self.glances_history.graph_enabled():
                args.enable_history = False
                logger.error('Stats history disabled because graph lib is not available')

N
Nicolargo 已提交
178 179 180 181 182 183 184 185 186 187 188 189
    def set_cursor(self, value):
        """Configure the cursor 
           0: invisible
           1: visible
           2: very visible
           """
        if hasattr(curses, 'curs_set'):
            try:
                curses.curs_set(value)
            except Exception:
                pass

A
Alessio Sergi 已提交
190
    def __get_key(self, window):
A
PEP 257  
Alessio Sergi 已提交
191
        # Catch ESC key AND numlock key (issue #163)
192 193 194 195 196 197 198 199 200 201
        keycode = [0, 0]
        keycode[0] = window.getch()
        keycode[1] = window.getch()

        if keycode[0] == 27 and keycode[1] != -1:
            # Do not escape on specials keys
            return -1
        else:
            return keycode[0]

A
Alessio Sergi 已提交
202
    def __catch_key(self):
A
PEP 257  
Alessio Sergi 已提交
203
        # Catch the pressed key
A
Alessio Sergi 已提交
204 205
        # ~ self.pressedkey = self.term_window.getch()
        self.pressedkey = self.__get_key(self.term_window)
206 207 208 209 210

        # Actions...
        if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
            # 'ESC'|'q' > Quit
            self.end()
N
Nicolas Hennion 已提交
211
            logger.info("Stop Glances")
212
            sys.exit(0)
N
Nicolargo 已提交
213 214 215
        elif self.pressedkey == 10:
            # 'ENTER' > Edit the process filter
            self.edit_filter = not self.edit_filter
216 217 218 219 220 221
        elif self.pressedkey == ord('1'):
            # '1' > Switch between CPU and PerCPU information
            self.args.percpu = not self.args.percpu
        elif self.pressedkey == ord('a'):
            # 'a' > Sort processes automatically
            self.args.process_sorted_by = 'auto'
N
Nicolargo 已提交
222
            glances_processes.resetsort()
223 224 225 226 227 228 229
        elif self.pressedkey == ord('b'):
            # 'b' > Switch between bit/s and Byte/s for network IO
            # self.net_byteps_tag = not self.net_byteps_tag
            self.args.byte = not self.args.byte
        elif self.pressedkey == ord('c'):
            # 'c' > Sort processes by CPU usage
            self.args.process_sorted_by = 'cpu_percent'
N
Nicolargo 已提交
230
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
231
        elif self.pressedkey == ord('d'):
232
            # 'd' > Show/hide disk I/O stats
233
            self.args.disable_diskio = not self.args.disable_diskio
N
Nicolargo 已提交
234 235 236 237 238 239 240
        elif self.pressedkey == ord('e'):
            # 'e' > Enable/Disable extended stats for top process
            self.args.disable_process_extended = not self.args.disable_process_extended
            if self.args.disable_process_extended:
                glances_processes.disable_extended()
            else:
                glances_processes.enable_extended()
241
        elif self.pressedkey == ord('f'):
242
            # 'f' > Show/hide fs stats
243
            self.args.disable_fs = not self.args.disable_fs
244 245 246
        elif self.pressedkey == ord('g'):
            # 'g' > History
            self.history_tag = not self.history_tag
247 248
        elif self.pressedkey == ord('h'):
            # 'h' > Show/hide help
249
            self.args.help_tag = not self.args.help_tag
250
        elif self.pressedkey == ord('i'):
251
            # 'i' > Sort processes by IO rate (not available on OS X)
252
            self.args.process_sorted_by = 'io_counters'
N
Nicolargo 已提交
253
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
254 255
        elif self.pressedkey == ord('l'):
            # 'l' > Show/hide log messages
256
            self.args.disable_log = not self.args.disable_log
257 258 259
        elif self.pressedkey == ord('m'):
            # 'm' > Sort processes by MEM usage
            self.args.process_sorted_by = 'memory_percent'
N
Nicolargo 已提交
260
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
261
        elif self.pressedkey == ord('n'):
262
            # 'n' > Show/hide network stats
263
            self.args.disable_network = not self.args.disable_network
264 265 266
        elif self.pressedkey == ord('p'):
            # 'p' > Sort processes by name
            self.args.process_sorted_by = 'name'
N
Nicolargo 已提交
267
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
268 269 270
        elif self.pressedkey == ord('r'):
            # 'r' > Reset history
            self.reset_history_tag = not self.reset_history_tag            
271 272
        elif self.pressedkey == ord('s'):
            # 's' > Show/hide sensors stats (Linux-only)
273
            self.args.disable_sensors = not self.args.disable_sensors
274
        elif self.pressedkey == ord('t'):
275 276
            # 't' > View network traffic as sum Rx+Tx
            self.args.network_sum = not self.args.network_sum
277
        elif self.pressedkey == ord('u'):
278 279
            # 'u' > View cumulative network IO (instead of bitrate)
            self.args.network_cumul = not self.args.network_cumul
280 281 282 283 284 285
        elif self.pressedkey == ord('w'):
            # 'w' > Delete finished warning logs
            glances_logs.clean()
        elif self.pressedkey == ord('x'):
            # 'x' > Delete finished warning and critical logs
            glances_logs.clean(critical=True)
286 287 288 289 290
        elif self.pressedkey == ord('z'):
            # 'z' > Enable/Disable processes stats (count + list + monitor)
            # Enable/Disable display
            self.args.disable_process = not self.args.disable_process
            # Enable/Disable update
291
            if self.args.disable_process:
292 293 294
                glances_processes.disable()
            else:
                glances_processes.enable()
295 296 297 298
        # Return the key code
        return self.pressedkey

    def end(self):
N
Nicolas Hennion 已提交
299
        """Shutdown the curses window."""        
N
Nicolas Hennion 已提交
300 301 302 303 304 305 306 307 308
        if hasattr(curses, 'echo'):
            curses.echo()
        if hasattr(curses, 'nocbreak'):
            curses.nocbreak()
        if hasattr(curses, 'curs_set'):
            try:
                curses.curs_set(1)
            except Exception:
                pass
309
        curses.endwin()
N
Nicolas Hennion 已提交
310
        
311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
    def init_line_column(self):
        """Init the line and column position for the curses inteface"""
        self.line = 0
        self.column = 0
        self.next_line = 0
        self.next_column = 0

    def init_line(self):
        """Init the line position for the curses inteface"""
        self.line = 0
        self.next_line = 0

    def init_column(self):
        """Init the column position for the curses inteface"""
        self.column = 0
        self.next_column = 0

    def new_line(self):
        """New line in the curses interface"""
        self.line = self.next_line

    def new_column(self):
        """New column in the curses interface"""
        self.column = self.next_column

336
    def display(self, stats, cs_status="None"):
A
PEP 257  
Alessio Sergi 已提交
337
        """Display stats on the screen.
338

339 340 341
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
342 343
            "Connected": Client is connected to a Glances server
            "SNMP": Client is connected to a SNMP server
344
            "Disconnected": Client is disconnected from the server
345 346 347 348

        Return:
            True if the stats have been displayed
            False if the help have been displayed
349
        """
350 351
        # Init the internal line/column for Glances Curses
        self.init_line_column()
352 353 354 355 356

        # Get the screen size
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]

357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
        # No processes list in SNMP mode
        if cs_status == 'SNMP':
            # so... more space for others plugins
            plugin_max_width = 43
        else:
            plugin_max_width = None

        # Update the stats messages
        ###########################

        # Update the client server status
        self.args.cs_status = cs_status
        stats_system = stats.get_plugin('system').get_stats_display(args=self.args)
        stats_uptime = stats.get_plugin('uptime').get_stats_display()
        if self.args.percpu:
            stats_percpu = stats.get_plugin('percpu').get_stats_display()
        else:
            stats_cpu = stats.get_plugin('cpu').get_stats_display()
        stats_load = stats.get_plugin('load').get_stats_display()
        stats_mem = stats.get_plugin('mem').get_stats_display()
        stats_memswap = stats.get_plugin('memswap').get_stats_display()
        stats_network = stats.get_plugin('network').get_stats_display(args=self.args, max_width=plugin_max_width)
        stats_diskio = stats.get_plugin('diskio').get_stats_display(args=self.args)
        stats_fs = stats.get_plugin('fs').get_stats_display(args=self.args, max_width=plugin_max_width)
        stats_sensors = stats.get_plugin('sensors').get_stats_display(args=self.args)
        stats_now = stats.get_plugin('now').get_stats_display()
        stats_processcount = stats.get_plugin('processcount').get_stats_display(args=self.args)
        stats_processlist = stats.get_plugin('processlist').get_stats_display(args=self.args)
        stats_monitor = stats.get_plugin('monitor').get_stats_display(args=self.args)
        stats_alert = stats.get_plugin('alert').get_stats_display(args=self.args)

        # Display the stats on the curses interface
        ###########################################

        # Help screen (on top of the other stats)
392
        if self.args.help_tag:
393
            # Display the stats...
394
            self.display_plugin(stats.get_plugin('help').get_stats_display(args=self.args))
395 396 397
            # ... and exit
            return False

398
        # Display first line (system+uptime)
399
        self.new_line()
400
        l = self.get_stats_display_width(stats_system) + self.get_stats_display_width(stats_uptime) + self.space_between_column
401
        self.display_plugin(stats_system, display_optional=(screen_x >= l))
402
        self.new_column()
403
        self.display_plugin(stats_uptime)
A
Alessio Sergi 已提交
404

405 406
        # Display second line (CPU|PERCPU+LOAD+MEM+SWAP+<SUMMARY>)
        # CPU|PERCPU
407 408
        self.init_column()
        self.new_line()        
409
        if self.args.percpu:
410
            l = self.get_stats_display_width(stats_percpu)
411
        else:
412 413
            l = self.get_stats_display_width(stats_cpu)
        l += self.get_stats_display_width(stats_load) + self.get_stats_display_width(stats_mem) + self.get_stats_display_width(stats_memswap)
414
        # Space between column
N
Nicolargo 已提交
415
        space_number = int(stats_load['msgdict'] != []) + int(stats_mem['msgdict'] != []) + int(stats_memswap['msgdict'] != []) 
416 417
        if space_number == 0:
            space_number = 1
N
Nicolargo 已提交
418 419
        if screen_x > (space_number * self.space_between_column + l):
            self.space_between_column = int((screen_x - l) / space_number)
420
        # Display
421
        if self.args.percpu:
422 423
            self.display_plugin(stats_percpu)
        else:
424
            self.display_plugin(stats_cpu, display_optional=(screen_x >= 80))
425
        self.new_column()
426
        self.display_plugin(stats_load)
427
        self.new_column()
N
Nicolargo 已提交
428
        self.display_plugin(stats_mem, display_optional=(screen_x >= (space_number * self.space_between_column + l)))
429
        self.new_column()
430 431 432 433
        self.display_plugin(stats_memswap)
        # Space between column
        self.space_between_column = 3

434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450
        # Backup line position
        self.saved_line = self.next_line

        # Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time)
        self.init_column()
        self.new_line()
        self.display_plugin(stats_network)
        self.new_line()
        self.display_plugin(stats_diskio)
        self.new_line()
        self.display_plugin(stats_fs)
        self.new_line()
        self.display_plugin(stats_sensors)
        self.new_line()
        self.display_plugin(stats_now)

        # If space available...
451
        if screen_x > 52:
452 453 454 455 456 457
            # Restore line position
            self.next_line = self.saved_line

            # Display right sidebar (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT)
            self.new_column()
            self.new_line()
458
            self.display_plugin(stats_processcount)
459
            self.new_line()
460
            self.display_plugin(stats_monitor)
461
            self.new_line()
A
Alessio Sergi 已提交
462
            self.display_plugin(stats_processlist,
463
                                display_optional=(screen_x > 102),
N
Nicolargo 已提交
464
                                display_additional=(is_mac == False),
A
Alessio Sergi 已提交
465
                                max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2))
466
            self.new_line()
467 468
            self.display_plugin(stats_alert)

469 470 471 472 473 474 475 476 477 478 479 480 481
        # History option
        # Generate history graph
        if self.history_tag and self.args.enable_history:
            self.display_popup(_("Graphs history generated in %s") % self.glances_history.get_output_folder())
            self.glances_history.generate_graph(stats)
        elif self.reset_history_tag and self.args.enable_history:
            self.display_popup(_("Reset history"))
            self.glances_history.reset(stats)            
        elif (self.history_tag or self.reset_history_tag) and not self.args.enable_history:
            self.display_popup(_("History disabled\nEnable it using --enable-history"))
        self.history_tag = False
        self.reset_history_tag = False

N
Nicolargo 已提交
482 483 484 485 486 487 488 489
        # Display edit filter popup
        if self.edit_filter:
            new_filter = self.display_popup(_("Filter: "), 
                                            is_input=True,
                                            input_value=glances_processes.get_process_filter())
            glances_processes.set_process_filter(new_filter)
        self.edit_filter = False

490 491
        return True

N
Nicolargo 已提交
492 493 494 495 496 497
    def display_popup(self, message, 
                      size_x=None, size_y=None, 
                      duration=3,
                      is_input=False,
                      input_size=20,
                      input_value=None):
498
        """
N
Nicolargo 已提交
499 500 501 502 503 504 505 506 507 508
        If is_input is False:
         Display a centered popup with the given message during duration seconds
         If size_x and size_y: set the popup size
         else set it automatically
         Return True if the popup could be displayed
        If is_input is True:
         Display a centered popup with the given message and a input field
         If size_x and size_y: set the popup size
         else set it automatically
         Return the input string or None if the field is empty        
509 510 511 512 513
        """

        # Center the popup
        if size_x is None:
            size_x = len(message) + 4
N
Nicolargo 已提交
514 515 516
            # Add space for the input field
            if is_input:
                size_x += input_size
517 518 519 520 521 522 523
        if size_y is None:
            size_y = message.count('\n') + 1 + 4
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]
        if size_x > screen_x or size_y > screen_y:
            # No size to display the popup => abord
            return False 
524 525
        pos_x = int((screen_x - size_x) / 2)
        pos_y = int((screen_y - size_y) / 2)
526 527 528

        # Create the popup
        popup = curses.newwin(size_y, size_x, pos_y, pos_x)
N
Nicolargo 已提交
529
        
530 531 532 533 534 535 536 537 538
        # Fill the popup
        popup.border()

        # Add the message
        y = 0
        for m in message.split('\n'):
            popup.addnstr(2 + y, 2, m, len(m))
            y += 1

N
Nicolargo 已提交
539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564
        if is_input:
            # Create a subwindow for the text field
            subpop = popup.derwin(1, input_size, 2, 2 + len(m))
            subpop.attron(self.__colors_list['FILTER'])
            # Init the field with the current value
            if input_value is not None:
                subpop.addnstr(0, 0, input_value, len(input_value))
            # Display the popup
            popup.refresh()
            subpop.refresh()
            # Create the textbox inside the subwindows
            self.set_cursor(2)
            textbox = glances_textbox(subpop, insert_mode=False)
            textbox.edit()
            self.set_cursor(0)
            if textbox.gather() != '':
                logger.debug(_("User enters the following process filter patern: %s") % textbox.gather())
                return textbox.gather()[:-1]
            else:
                logger.debug(_("User clears the process filter patern"))
                return None
        else:
            # Display the popup
            popup.refresh()
            curses.napms(duration * 1000)
            return True
565

566 567 568 569
    def display_plugin(self, plugin_stats, 
                       display_optional=True,
                       display_additional=True, 
                       max_y=65535):
A
PEP 257  
Alessio Sergi 已提交
570 571
        """Display the plugin_stats on the screen.

572 573
        If display_optional=True display the optional stats
        If display_additional=True display additionnal stats
574 575
        max_y do not display line > max_y
        """
576 577 578
        # Exit if:
        # - the plugin_stats message is empty
        # - the display tag = False
579
        if plugin_stats['msgdict'] == [] or not plugin_stats['display']:
580
            # Exit
581 582 583 584 585 586 587
            return 0

        # Get the screen size
        screen_x = self.screen.getmaxyx()[1]
        screen_y = self.screen.getmaxyx()[0]

        # Set the upper/left position of the message
588
        if plugin_stats['align'] == 'right':
589
            # Right align (last column)
590
            display_x = screen_x - self.get_stats_display_width(plugin_stats)
591
        else:
592
            display_x = self.column
593
        if plugin_stats['align'] == 'bottom':
594
            # Bottom (last line)
595
            display_y = screen_y - self.get_stats_display_height(plugin_stats)
596
        else:
597
            display_y = self.line
N
Nicolargo 已提交
598
        
599 600 601 602 603
        # Display
        x = display_x
        y = display_y
        for m in plugin_stats['msgdict']:
            # New line
604
            if m['msg'].startswith('\n'):
605 606 607 608 609 610
                # Go to the next line
                y = y + 1
                # Return to the first column
                x = display_x
                continue
            # Do not display outside the screen
611
            if x < 0:
612
                continue
613
            if not m['splittable'] and (x + len(m['msg']) > screen_x):
614
                continue
615
            if y < 0 or (y + 1 > screen_y) or (y > max_y):
616 617
                break
            # If display_optional = False do not display optional stats
618
            if not display_optional and m['optional']:
619
                continue
620 621 622
            # If display_additional = False do not display additional stats
            if not display_additional and m['additional']:
                continue
623 624 625
            # Is it possible to display the stat with the current screen size
            # !!! Crach if not try/except... Why ???
            try:
A
Alessio Sergi 已提交
626 627 628
                self.term_window.addnstr(y, x,
                                         m['msg'],
                                         screen_x - x,  # Do not disply outside the screen
629
                                         self.__colors_list[m['decoration']])                
630 631 632 633 634 635 636
            except:
                pass
            else:
                # New column
                x = x + len(m['msg'])

        # Compute the next Glances column/line position
637 638
        self.next_column = max(self.next_column, x + self.space_between_column)
        self.next_line = max(self.next_line, y + self.space_between_line)
639 640

    def erase(self):
A
PEP 257  
Alessio Sergi 已提交
641
        """Erase the content of the screen."""
642 643
        self.term_window.erase()

644
    def flush(self, stats, cs_status="None"):
A
PEP 257  
Alessio Sergi 已提交
645 646
        """Clear and update the screen.

647 648 649 650 651 652 653
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
            "Connected": Client is connected to the server
            "Disconnected": Client is disconnected from the server
        """
        self.erase()
654
        self.display(stats, cs_status=cs_status)
655

656
    def update(self, stats, cs_status="None"):
A
PEP 257  
Alessio Sergi 已提交
657 658 659 660
        """Update the screen.

        Wait for __refresh_time sec / catch key every 100 ms.

661 662 663 664 665 666 667
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
            "Connected": Client is connected to the server
            "Disconnected": Client is disconnected from the server
        """
        # Flush display
668
        self.flush(stats, cs_status=cs_status)
669 670 671

        # Wait
        countdown = Timer(self.__refresh_time)
A
PEP 257  
Alessio Sergi 已提交
672
        while not countdown.finished():
673
            # Getkey
A
Alessio Sergi 已提交
674
            if self.__catch_key() > -1:
675 676
                # Redraw display
                self.flush(stats, cs_status=cs_status)                
677 678 679
            # Wait 100ms...
            curses.napms(100)

680
    def get_stats_display_width(self, curse_msg, without_option=False):
A
PEP 257  
Alessio Sergi 已提交
681
        """Return the width of the formatted curses message.
682

A
PEP 257  
Alessio Sergi 已提交
683 684
        The height is defined by the maximum line.
        """
685
        try:
686
            if without_option:
687
                # Size without options
A
Alessio Sergi 已提交
688 689
                c = len(max(''.join([(i['msg'] if not i['optional'] else "")
                        for i in curse_msg['msgdict']]).split('\n'), key=len))
690 691
            else:
                # Size with all options
A
Alessio Sergi 已提交
692 693
                c = len(max(''.join([i['msg']
                        for i in curse_msg['msgdict']]).split('\n'), key=len))
694 695 696 697 698
        except:
            return 0
        else:
            return c

699
    def get_stats_display_height(self, curse_msg):
A
PEP 257  
Alessio Sergi 已提交
700
        r"""Return the height of the formatted curses message.
701

A
PEP 257  
Alessio Sergi 已提交
702 703
        The height is defined by the number of '\n' (new line).
        """
704
        try:
A
Alessio Sergi 已提交
705
            c = [i['msg'] for i in curse_msg['msgdict']].count('\n')
706 707 708 709
        except:
            return 0
        else:
            return c + 1
N
Nicolargo 已提交
710 711 712 713 714 715 716 717 718 719 720 721 722

class glances_textbox(Textbox):
    """
    """
    def __init__(*args, **kwargs):
        Textbox.__init__(*args, **kwargs)
  
    def do_command(self, ch):
        if ch == 10: # Enter
            return 0
        if ch == 127: # Enter
            return 8
        return Textbox.do_command(self, ch)