glances_curses.py 29.6 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
            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)
N
Nicolargo 已提交
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
            try:
                curses.init_pair(9, curses.COLOR_MAGENTA, -1)
            except:
                if args.theme_white:
                    curses.init_pair(9, curses.COLOR_BLACK, -1)
                else:
                    curses.init_pair(9, curses.COLOR_WHITE, -1)
            try:
                curses.init_pair(10, curses.COLOR_CYAN, -1)
            except:
                if args.theme_white:
                    curses.init_pair(10, curses.COLOR_BLACK, -1)
                else:
                    curses.init_pair(10, curses.COLOR_WHITE, -1)

107 108 109
        else:
            self.hascolors = False

A
Alessio Sergi 已提交
110
        if args.disable_bold:
111 112 113 114 115 116 117 118 119 120
            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 已提交
121
            self.default_color = curses.color_pair(3) | A_BOLD
122
            self.nice_color = curses.color_pair(9) | A_BOLD
123 124 125 126 127 128 129
            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
N
Nicolargo 已提交
130
            self.filter_color = curses.color_pair(10) | A_BOLD
131 132 133 134
        else:
            # B&W text styles
            self.no_color = curses.A_NORMAL
            self.default_color = curses.A_NORMAL
135
            self.nice_color = A_BOLD
136 137 138 139 140 141 142
            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
N
Nicolargo 已提交
143
            self.filter_color = A_BOLD
144 145 146 147 148 149

        # Define the colors list (hash table) for stats
        self.__colors_list = {
            'DEFAULT': self.no_color,
            'UNDERLINE': curses.A_UNDERLINE,
            'BOLD': A_BOLD,
150
            'SORT': A_BOLD,
151
            'OK': self.default_color2,
N
Nicolargo 已提交
152
            'FILTER': self.filter_color,
153
            'TITLE': self.title_color,
154 155
            'PROCESS': self.default_color2,
            'STATUS': self.default_color2,
156
            'NICE': self.nice_color,
157 158 159 160 161 162 163 164 165 166 167 168 169
            '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 已提交
170
        self.__refresh_time = args.time
171 172 173 174

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

N
Nicolargo 已提交
175 176 177
        # Init edit filter tag
        self.edit_filter = False

178 179 180 181 182
        # Catch key pressed with non blocking mode
        self.term_window.keypad(1)
        self.term_window.nodelay(1)
        self.pressedkey = -1

183 184 185 186 187 188 189 190 191 192 193
        # 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 已提交
194 195 196 197 198 199 200 201 202 203 204 205
    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 已提交
206
    def __get_key(self, window):
A
PEP 257  
Alessio Sergi 已提交
207
        # Catch ESC key AND numlock key (issue #163)
208 209 210 211 212 213 214 215 216 217
        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 已提交
218
    def __catch_key(self):
A
PEP 257  
Alessio Sergi 已提交
219
        # Catch the pressed key
A
Alessio Sergi 已提交
220 221
        # ~ self.pressedkey = self.term_window.getch()
        self.pressedkey = self.__get_key(self.term_window)
222 223 224 225 226

        # Actions...
        if self.pressedkey == ord('\x1b') or self.pressedkey == ord('q'):
            # 'ESC'|'q' > Quit
            self.end()
N
Nicolas Hennion 已提交
227
            logger.info("Stop Glances")
228
            sys.exit(0)
N
Nicolargo 已提交
229 230 231
        elif self.pressedkey == 10:
            # 'ENTER' > Edit the process filter
            self.edit_filter = not self.edit_filter
232 233 234
        elif self.pressedkey == ord('1'):
            # '1' > Switch between CPU and PerCPU information
            self.args.percpu = not self.args.percpu
235 236 237
        elif self.pressedkey == ord('2'):
            # '2' > Enable/disable left sidebar
            self.args.disable_left_sidebar = not self.args.disable_left_sidebar    
238 239 240
        elif self.pressedkey == ord('/'):
            # '/' > Switch between short/long name for processes
            self.args.process_short_name = not self.args.process_short_name
241 242 243
        elif self.pressedkey == ord('a'):
            # 'a' > Sort processes automatically
            self.args.process_sorted_by = 'auto'
N
Nicolargo 已提交
244
            glances_processes.resetsort()
245 246 247 248 249 250 251
        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 已提交
252
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
253
        elif self.pressedkey == ord('d'):
254
            # 'd' > Show/hide disk I/O stats
255
            self.args.disable_diskio = not self.args.disable_diskio
N
Nicolargo 已提交
256 257 258 259 260 261 262
        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()
263
        elif self.pressedkey == ord('f'):
264
            # 'f' > Show/hide fs stats
265
            self.args.disable_fs = not self.args.disable_fs
266 267 268
        elif self.pressedkey == ord('g'):
            # 'g' > History
            self.history_tag = not self.history_tag
269 270
        elif self.pressedkey == ord('h'):
            # 'h' > Show/hide help
271
            self.args.help_tag = not self.args.help_tag
272
        elif self.pressedkey == ord('i'):
273
            # 'i' > Sort processes by IO rate (not available on OS X)
274
            self.args.process_sorted_by = 'io_counters'
N
Nicolargo 已提交
275
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
276 277
        elif self.pressedkey == ord('l'):
            # 'l' > Show/hide log messages
278
            self.args.disable_log = not self.args.disable_log
279 280 281
        elif self.pressedkey == ord('m'):
            # 'm' > Sort processes by MEM usage
            self.args.process_sorted_by = 'memory_percent'
N
Nicolargo 已提交
282
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
283
        elif self.pressedkey == ord('n'):
284
            # 'n' > Show/hide network stats
285
            self.args.disable_network = not self.args.disable_network
286 287 288
        elif self.pressedkey == ord('p'):
            # 'p' > Sort processes by name
            self.args.process_sorted_by = 'name'
N
Nicolargo 已提交
289
            glances_processes.setmanualsortkey(self.args.process_sorted_by)
290 291 292
        elif self.pressedkey == ord('r'):
            # 'r' > Reset history
            self.reset_history_tag = not self.reset_history_tag            
293 294
        elif self.pressedkey == ord('s'):
            # 's' > Show/hide sensors stats (Linux-only)
295
            self.args.disable_sensors = not self.args.disable_sensors
296
        elif self.pressedkey == ord('t'):
297 298
            # 't' > View network traffic as sum Rx+Tx
            self.args.network_sum = not self.args.network_sum
299
        elif self.pressedkey == ord('u'):
300 301
            # 'u' > View cumulative network IO (instead of bitrate)
            self.args.network_cumul = not self.args.network_cumul
302 303 304 305 306 307
        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)
308 309 310 311 312
        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
313
            if self.args.disable_process:
314 315 316
                glances_processes.disable()
            else:
                glances_processes.enable()
317 318 319 320
        # Return the key code
        return self.pressedkey

    def end(self):
N
Nicolas Hennion 已提交
321
        """Shutdown the curses window."""        
N
Nicolas Hennion 已提交
322 323 324 325 326 327 328 329 330
        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
331
        curses.endwin()
N
Nicolas Hennion 已提交
332
        
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357
    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

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

361 362 363
        stats: Stats database to display
        cs_status:
            "None": standalone or server mode
364 365
            "Connected": Client is connected to a Glances server
            "SNMP": Client is connected to a SNMP server
366
            "Disconnected": Client is disconnected from the server
367 368 369 370

        Return:
            True if the stats have been displayed
            False if the help have been displayed
371
        """
372 373
        # Init the internal line/column for Glances Curses
        self.init_line_column()
374 375 376 377 378

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

379 380 381 382 383 384 385 386 387 388
        # 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
        ###########################

389

390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409
        # 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_monitor = stats.get_plugin('monitor').get_stats_display(args=self.args)
        stats_alert = stats.get_plugin('alert').get_stats_display(args=self.args)

410 411 412 413 414 415 416 417 418 419 420 421 422
        # Adapt number of processes to the available space
        max_processes_displayed = screen_y - 11 - self.get_stats_display_height(stats_alert)
        if not self.args.disable_process_extended:
            max_processes_displayed -= 4
        if max_processes_displayed < 0:
            max_processes_displayed = 0            
        if glances_processes.get_max_processes() is None or \
           glances_processes.get_max_processes() != max_processes_displayed:
            logger.debug(_("Set number of displayed processes to %s") % max_processes_displayed)
            glances_processes.set_max_processes(max_processes_displayed)

        stats_processlist = stats.get_plugin('processlist').get_stats_display(args=self.args)

423 424 425 426
        # Display the stats on the curses interface
        ###########################################

        # Help screen (on top of the other stats)
427
        if self.args.help_tag:
428
            # Display the stats...
429
            self.display_plugin(stats.get_plugin('help').get_stats_display(args=self.args))
430 431 432
            # ... and exit
            return False

433
        # Display first line (system+uptime)
434
        self.new_line()
435
        l = self.get_stats_display_width(stats_system) + self.get_stats_display_width(stats_uptime) + self.space_between_column
436
        self.display_plugin(stats_system, display_optional=(screen_x >= l))
437
        self.new_column()
438
        self.display_plugin(stats_uptime)
A
Alessio Sergi 已提交
439

440 441
        # Display second line (CPU|PERCPU+LOAD+MEM+SWAP+<SUMMARY>)
        # CPU|PERCPU
442 443
        self.init_column()
        self.new_line()        
444
        if self.args.percpu:
445
            l = self.get_stats_display_width(stats_percpu)
446
        else:
447 448
            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)
449
        # Space between column
N
Nicolargo 已提交
450
        space_number = int(stats_load['msgdict'] != []) + int(stats_mem['msgdict'] != []) + int(stats_memswap['msgdict'] != []) 
451 452
        if space_number == 0:
            space_number = 1
N
Nicolargo 已提交
453 454
        if screen_x > (space_number * self.space_between_column + l):
            self.space_between_column = int((screen_x - l) / space_number)
455
        # Display
456
        if self.args.percpu:
457 458
            self.display_plugin(stats_percpu)
        else:
459
            self.display_plugin(stats_cpu, display_optional=(screen_x >= 80))
460
        self.new_column()
461
        self.display_plugin(stats_load)
462
        self.new_column()
N
Nicolargo 已提交
463
        self.display_plugin(stats_mem, display_optional=(screen_x >= (space_number * self.space_between_column + l)))
464
        self.new_column()
465 466 467 468
        self.display_plugin(stats_memswap)
        # Space between column
        self.space_between_column = 3

469 470 471 472 473
        # Backup line position
        self.saved_line = self.next_line

        # Display left sidebar (NETWORK+DISKIO+FS+SENSORS+Current time)
        self.init_column()
474 475 476 477 478 479 480 481 482 483 484 485 486
        if (not (self.args.disable_network and self.args.disable_diskio \
            and self.args.disable_fs and self.args.disable_sensors)) \
            and not self.args.disable_left_sidebar:
            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)
487 488

        # If space available...
489
        if screen_x > 52:
490 491 492 493 494 495
            # Restore line position
            self.next_line = self.saved_line

            # Display right sidebar (PROCESS_COUNT+MONITORED+PROCESS_LIST+ALERT)
            self.new_column()
            self.new_line()
496
            self.display_plugin(stats_processcount)
N
Nicolargo 已提交
497 498 499 500
            if glances_processes.get_process_filter() == None and cs_status == 'None':
                # Do not display stats monitor list if a filter exist
                self.new_line()
                self.display_plugin(stats_monitor)
501
            self.new_line()
A
Alessio Sergi 已提交
502
            self.display_plugin(stats_processlist,
503
                                display_optional=(screen_x > 102),
N
Nicolargo 已提交
504
                                display_additional=(is_mac == False),
A
Alessio Sergi 已提交
505
                                max_y=(screen_y - self.get_stats_display_height(stats_alert) - 2))
506
            self.new_line()
507 508
            self.display_plugin(stats_alert)

509 510 511 512 513 514 515 516 517 518 519 520 521
        # 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 已提交
522
        # Display edit filter popup
523 524
        # Only in standalone mode (cs_status == 'None')
        if self.edit_filter and cs_status == 'None':
N
Nicolargo 已提交
525
            new_filter = self.display_popup(_("Process filter pattern: "), 
N
Nicolargo 已提交
526 527 528
                                            is_input=True,
                                            input_value=glances_processes.get_process_filter())
            glances_processes.set_process_filter(new_filter)
529 530
        elif self.edit_filter and cs_status != 'None':
            self.display_popup(_("Process filter only available in standalone mode"))
N
Nicolargo 已提交
531 532
        self.edit_filter = False

533 534
        return True

N
Nicolargo 已提交
535 536 537 538
    def display_popup(self, message, 
                      size_x=None, size_y=None, 
                      duration=3,
                      is_input=False,
N
Nicolargo 已提交
539
                      input_size=30,
N
Nicolargo 已提交
540
                      input_value=None):
541
        """
N
Nicolargo 已提交
542 543 544 545 546 547 548 549 550 551
        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        
552 553 554 555 556
        """

        # Center the popup
        if size_x is None:
            size_x = len(message) + 4
N
Nicolargo 已提交
557 558 559
            # Add space for the input field
            if is_input:
                size_x += input_size
560 561 562 563 564 565 566
        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 
567 568
        pos_x = int((screen_x - size_x) / 2)
        pos_y = int((screen_y - size_y) / 2)
569 570 571

        # Create the popup
        popup = curses.newwin(size_y, size_x, pos_y, pos_x)
N
Nicolargo 已提交
572
        
573 574 575 576 577 578 579 580 581
        # 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 已提交
582
        if is_input and not is_windows:
N
Nicolargo 已提交
583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
            # 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
608

609 610 611 612
    def display_plugin(self, plugin_stats, 
                       display_optional=True,
                       display_additional=True, 
                       max_y=65535):
A
PEP 257  
Alessio Sergi 已提交
613 614
        """Display the plugin_stats on the screen.

615 616
        If display_optional=True display the optional stats
        If display_additional=True display additionnal stats
617 618
        max_y do not display line > max_y
        """
619 620 621
        # Exit if:
        # - the plugin_stats message is empty
        # - the display tag = False
622
        if plugin_stats['msgdict'] == [] or not plugin_stats['display']:
623
            # Exit
624 625 626 627 628 629 630
            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
631
        if plugin_stats['align'] == 'right':
632
            # Right align (last column)
633
            display_x = screen_x - self.get_stats_display_width(plugin_stats)
634
        else:
635
            display_x = self.column
636
        if plugin_stats['align'] == 'bottom':
637
            # Bottom (last line)
638
            display_y = screen_y - self.get_stats_display_height(plugin_stats)
639
        else:
640
            display_y = self.line
N
Nicolargo 已提交
641
        
642 643 644 645 646
        # Display
        x = display_x
        y = display_y
        for m in plugin_stats['msgdict']:
            # New line
647
            if m['msg'].startswith('\n'):
648 649 650 651 652 653
                # Go to the next line
                y = y + 1
                # Return to the first column
                x = display_x
                continue
            # Do not display outside the screen
654
            if x < 0:
655
                continue
656
            if not m['splittable'] and (x + len(m['msg']) > screen_x):
657
                continue
658
            if y < 0 or (y + 1 > screen_y) or (y > max_y):
659 660
                break
            # If display_optional = False do not display optional stats
661
            if not display_optional and m['optional']:
662
                continue
663 664 665
            # If display_additional = False do not display additional stats
            if not display_additional and m['additional']:
                continue
666 667 668
            # Is it possible to display the stat with the current screen size
            # !!! Crach if not try/except... Why ???
            try:
A
Alessio Sergi 已提交
669 670 671
                self.term_window.addnstr(y, x,
                                         m['msg'],
                                         screen_x - x,  # Do not disply outside the screen
672
                                         self.__colors_list[m['decoration']])                
673 674 675 676 677 678 679
            except:
                pass
            else:
                # New column
                x = x + len(m['msg'])

        # Compute the next Glances column/line position
680 681
        self.next_column = max(self.next_column, x + self.space_between_column)
        self.next_line = max(self.next_line, y + self.space_between_line)
682 683

    def erase(self):
A
PEP 257  
Alessio Sergi 已提交
684
        """Erase the content of the screen."""
685 686
        self.term_window.erase()

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

690 691 692 693 694 695 696
        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()
697
        self.display(stats, cs_status=cs_status)
698

699
    def update(self, stats, cs_status="None"):
A
PEP 257  
Alessio Sergi 已提交
700 701 702 703
        """Update the screen.

        Wait for __refresh_time sec / catch key every 100 ms.

704 705 706 707 708 709 710
        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
711
        self.flush(stats, cs_status=cs_status)
712 713 714

        # Wait
        countdown = Timer(self.__refresh_time)
A
PEP 257  
Alessio Sergi 已提交
715
        while not countdown.finished():
716
            # Getkey
A
Alessio Sergi 已提交
717
            if self.__catch_key() > -1:
718 719
                # Redraw display
                self.flush(stats, cs_status=cs_status)                
720 721 722
            # Wait 100ms...
            curses.napms(100)

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

A
PEP 257  
Alessio Sergi 已提交
726 727
        The height is defined by the maximum line.
        """
728
        try:
729
            if without_option:
730
                # Size without options
A
Alessio Sergi 已提交
731 732
                c = len(max(''.join([(i['msg'] if not i['optional'] else "")
                        for i in curse_msg['msgdict']]).split('\n'), key=len))
733 734
            else:
                # Size with all options
A
Alessio Sergi 已提交
735 736
                c = len(max(''.join([i['msg']
                        for i in curse_msg['msgdict']]).split('\n'), key=len))
737 738 739 740 741
        except:
            return 0
        else:
            return c

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

A
PEP 257  
Alessio Sergi 已提交
745 746
        The height is defined by the number of '\n' (new line).
        """
747
        try:
A
Alessio Sergi 已提交
748
            c = [i['msg'] for i in curse_msg['msgdict']].count('\n')
749 750 751 752
        except:
            return 0
        else:
            return c + 1
N
Nicolargo 已提交
753

N
Nicolargo 已提交
754 755 756 757 758 759 760 761 762 763 764 765 766
if not is_windows:
    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)