fs.py (26597B)
1 import collections 2 from datetime import datetime, timedelta 3 import re 4 5 from pyxp import * 6 from pyxp.client import * 7 from pygmi import * 8 from pygmi.util import prop 9 10 __all__ = ('wmii', 'Tags', 'Tag', 'Area', 'Frame', 'Client', 11 'Button', 'Colors', 'Color', 'Toggle', 'Always', 'Never') 12 13 spacere = re.compile(r'\s') 14 sentinel = {} 15 16 def tounicode(obj): 17 if isinstance(obj, str): 18 return obj.decode('UTF-8') 19 return unicode(obj) 20 21 class utf8(object): 22 def __str__(self): 23 return unicode(self).encode('utf-8') 24 25 @apply 26 class Toggle(utf8): 27 def __unicode__(self): 28 return unicode(self.__class__.__name__) 29 @apply 30 class Always(Toggle.__class__): 31 pass 32 @apply 33 class Never(Toggle.__class__): 34 pass 35 36 def constrain(min, max, val): 37 return min if val < min else max if val > max else val 38 39 class Map(collections.Mapping): 40 def __init__(self, cls, *args): 41 self.cls = cls 42 self.args = args 43 def __repr__(self): 44 return 'Map(%s%s)' % (self.cls.__name__, (', %s' % ', '.join(map(repr, self.args)) if self.args else '')) 45 def __getitem__(self, item): 46 ret = self.cls(*(self.args + (item,))) 47 if not ret.exists: 48 raise KeyError('no such %s %s' % (self.cls.__name__.lower(), repr(item))) 49 return ret 50 def __len__(self): 51 return len(iter(self)) 52 def __keys__(self): 53 return [v for v in self.cls.all(*self.args)] 54 def __iter__(self): 55 return (v for v in self.cls.all(*self.args)) 56 def iteritems(self): 57 return ((v, self.cls(*(self.args + (v,)))) for v in self.cls.all(*self.args)) 58 def itervalues(self): 59 return (self.cls(*(self.args + (v,))) for v in self.cls.all(*self.args)) 60 61 class Ctl(object): 62 """ 63 An abstract class to represent the 'ctl' files of the wmii filesystem. 64 Instances act as live, writable dictionaries of the settings represented 65 in the file. 66 67 Abstract roperty ctl_path: The path to the file represented by this 68 control. 69 Property ctl_hasid: When true, the first line of the represented 70 file is treated as an id, rather than a key-value pair. In this 71 case, the value is available via the 'id' property. 72 Property ctl_types: A dict mapping named dictionary keys to two valued 73 tuples, each containing a decoder and encoder function for the 74 property's plain text value. 75 """ 76 ctl_types = {} 77 ctl_hasid = False 78 ctl_open = 'aopen' 79 ctl_file = None 80 81 def __eq__(self, other): 82 if self.ctl_hasid and isinstance(other, Ctl) and other.ctl_hasid: 83 return self.id == other.id 84 return False 85 86 def __init__(self): 87 self.cache = {} 88 89 def ctl(self, *args): 90 """ 91 Arguments are joined by ascii spaces and written to the ctl file. 92 """ 93 def next(file): 94 if file: 95 self.ctl_file = file 96 file.awrite(u' '.join(map(tounicode, args))) 97 if self.ctl_file: 98 return next(self.ctl_file) 99 getattr(client, self.ctl_open)(self.ctl_path, callback=next, mode=OWRITE) 100 101 def __getitem__(self, key): 102 for line in self.ctl_lines(): 103 key_, rest = line.split(' ', 1) 104 if key_ == key: 105 if key in self.ctl_types: 106 return self.ctl_types[key][0](rest) 107 return rest 108 raise KeyError() 109 def __hasitem__(self, key): 110 return key in self.keys() 111 def __setitem__(self, key, val): 112 assert '\n' not in key 113 self.cache[key] = val 114 if key in self.ctl_types: 115 if self.ctl_types[key][1] is None: 116 raise NotImplementedError('%s: %s is not writable' % (self.ctl_path, key)) 117 val = self.ctl_types[key][1](val) 118 self.ctl(key, val) 119 120 def get(self, key, default=sentinel): 121 """ 122 Gets the instance's dictionary value for 'key'. If the key doesn't 123 exist, 'default' is returned. If 'default' isn't provided and the key 124 doesn't exist, a KeyError is raised. 125 """ 126 try: 127 return self[key] 128 except KeyError, e: 129 if default is not self.sentinel: 130 return default 131 raise e 132 def set(self, key, val): 133 """ 134 Sets the dictionary value for 'key' to 'val', as self[key] = val 135 """ 136 self[key] = val 137 138 def keys(self): 139 return [line.split(' ', 1)[0] 140 for line in self.ctl_lines()] 141 def iteritems(self): 142 return (tuple(line.split(' ', 1)) 143 for line in self.ctl_lines()) 144 def items(self): 145 return [tuple(line.split(' ', 1)) 146 for line in self.ctl_lines()] 147 148 def ctl_lines(self): 149 """ 150 Returns the lines of the ctl file as a tuple, with the first line 151 stripped if #ctl_hasid is set. 152 """ 153 lines = tuple(client.readlines(self.ctl_path)) 154 if self.ctl_hasid: 155 lines = lines[1:] 156 return lines 157 158 _id = None 159 @prop(doc="If #ctl_hasid is set, returns the id of this ctl file.") 160 def id(self): 161 if self._id is None and self.ctl_hasid: 162 return self.name_read(client.read(self.ctl_path).split('\n', 1)[0]) 163 return self._id 164 165 class Dir(Ctl): 166 """ 167 An abstract class representing a directory in the wmii filesystem with a 168 ctl file and sub-objects. 169 170 Abstract property base_path: The path directly under which all objects 171 represented by this class reside. e.g., /client, /tag 172 """ 173 ctl_hasid = True 174 name_read = unicode 175 name_write = unicode 176 177 def __init__(self, id): 178 """ 179 Initializes the directory object. 180 181 Param id: The id of the object in question. If 'sel', the object 182 dynamically represents the selected object, even as it 183 changes. In this case, #id will return the actual ID of the 184 object. 185 """ 186 super(Dir, self).__init__() 187 if isinstance(id, Dir): 188 id = id.id 189 if id != 'sel': 190 self._id = self.name_read(id) 191 192 def __eq__(self, other): 193 return (self.__class__ == other.__class__ and 194 self.id == other.id) 195 196 class ctl_property(object): 197 """ 198 A class which maps instance properties to ctl file properties. 199 """ 200 def __init__(self, key): 201 self.key = key 202 def __get__(self, dir, cls): 203 return dir.get(self.key, None) 204 def __set__(self, dir, val): 205 dir[self.key] = val 206 207 class toggle_property(ctl_property): 208 """ 209 A class which maps instance properties to ctl file properties. The 210 values True and False map to the strings "on" and "off" in the 211 filesystem. 212 """ 213 props = { 214 'on': True, 215 'off': False, 216 'toggle': Toggle, 217 'always': Always, 218 'never': Never 219 } 220 def __get__(self, dir, cls): 221 val = dir[self.key] 222 if val in self.props: 223 return self.props[val] 224 return val 225 def __set__(self, dir, val): 226 for k, v in self.props.iteritems(): 227 if v == val: 228 val = k 229 break 230 dir[self.key] = val 231 232 class file_property(object): 233 """ 234 A class which maps instance properties to files in the directory 235 represented by this object. 236 """ 237 def __init__(self, name, writable=False): 238 self.name = name 239 self.writable = writable 240 def __get__(self, dir, cls): 241 return client.read('%s/%s' % (dir.path, self.name)) 242 def __set__(self, dir, val): 243 if not self.writable: 244 raise NotImplementedError('File %s is not writable' % self.name) 245 return client.awrite('%s/%s' % (dir.path, self.name), 246 str(val)) 247 248 @prop(doc="The path to this directory's ctl file") 249 def ctl_path(self): 250 return '%s/ctl' % self.path 251 252 @prop(doc="The path to this directory") 253 def path(self): 254 return '%s/%s' % (self.base_path, self.name_write(self._id or 'sel')) 255 @prop(doc="True if the given object exists in the wmii filesystem") 256 def exists(self): 257 return bool(client.stat(self.path)) 258 259 @classmethod 260 def all(cls): 261 """ 262 Returns all of the objects that exist for this type of directory. 263 """ 264 return (cls.name_read(s.name) 265 for s in client.readdir(cls.base_path) 266 if s.name != 'sel') 267 @classmethod 268 def map(cls, *args): 269 return Map(cls, *args) 270 271 def __repr__(self): 272 return '%s(%s)' % (self.__class__.__name__, 273 repr(self._id or 'sel')) 274 275 class Client(Dir): 276 """ 277 A class which represents wmii clients. Maps to the directories directly 278 below /client. 279 """ 280 base_path = '/client' 281 ctl_types = { 282 'group': (lambda s: int(s, 16), str), 283 'pid': (int, None), 284 } 285 @staticmethod 286 def name_read(name): 287 if isinstance(name, int): 288 return name 289 try: 290 return int(name, 16) 291 except: 292 return unicode(name) 293 name_write = lambda self, name: name if isinstance(name, basestring) else '%#x' % name 294 295 allow = Dir.ctl_property('allow') 296 fullscreen = Dir.toggle_property('fullscreen') 297 group = Dir.ctl_property('group') 298 pid = Dir.ctl_property('pid') 299 tags = Dir.ctl_property('tags') 300 urgent = Dir.toggle_property('urgent') 301 302 label = Dir.file_property('label', writable=True) 303 props = Dir.file_property('props') 304 305 def kill(self): 306 """Politely asks a client to quit.""" 307 self.ctl('kill') 308 309 def slay(self): 310 """Forcibly severs a client's connection to the X server.""" 311 self.ctl('slay') 312 313 class liveprop(object): 314 def __init__(self, get): 315 self.get = get 316 self.attr = str(self) 317 def __get__(self, area, cls): 318 if getattr(area, self.attr, sentinel) is not sentinel: 319 return getattr(area, self.attr) 320 return self.get(area) 321 def __set__(self, area, val): 322 setattr(area, self.attr, val) 323 324 class Area(object): 325 def __init__(self, tag, ord, screen='sel', offset=sentinel, width=sentinel, height=sentinel, frames=sentinel): 326 self.tag = tag 327 if ':' in str(ord): 328 screen, ord = ord.split(':', 2) 329 self.ord = str(ord) 330 self.screen = str(screen) 331 self.offset = offset 332 self.width = width 333 self.height = height 334 self.frames = frames 335 336 def prop(key): 337 @liveprop 338 def prop(self): 339 for area in self.tag.index: 340 if str(area.ord) == str(self.ord): 341 return getattr(area, key) 342 return prop 343 offset = prop('offset') 344 width = prop('width') 345 height = prop('height') 346 frames = prop('frames') 347 348 @property 349 def spec(self): 350 if self.screen is not None: 351 return '%s:%s' % (self.screen, self.ord) 352 return self.ord 353 354 @property 355 def mode(self): 356 for k, v in self.tag.iteritems(): 357 if k == 'colmode': 358 v = v.split(' ') 359 if v[0] == self.ord: 360 return v[1] 361 @mode.setter 362 def mode(self, val): 363 self.tag['colmode %s' % self.spec] = val 364 365 def grow(self, dir, amount=None): 366 self.tag.grow(self, dir, amount) 367 def nudge(self, dir, amount=None): 368 self.tag.nudge(self, dir, amount) 369 370 class Frame(object): 371 live = False 372 373 def __init__(self, client, area=sentinel, ord=sentinel, offset=sentinel, height=sentinel): 374 self.client = client 375 self.ord = ord 376 self.offset = offset 377 self.height = height 378 379 @property 380 def width(self): 381 return self.area.width 382 383 def prop(key): 384 @liveprop 385 def prop(self): 386 for area in self.tag.index: 387 for frame in area.frames: 388 if frame.client == self.client: 389 return getattr(frame, key) 390 return prop 391 offset = prop('area') 392 offset = prop('ord') 393 offset = prop('offset') 394 height = prop('height') 395 396 def grow(self, dir, amount=None): 397 self.area.tag.grow(self, dir, amount) 398 def nudge(self, dir, amount=None): 399 self.area.tag.nudge(self, dir, amount) 400 401 class Tag(Dir): 402 base_path = '/tag' 403 404 @classmethod 405 def framespec(cls, frame): 406 if isinstance(frame, Frame): 407 frame = frame.client 408 if isinstance(frame, Area): 409 frame = (frame.ord, 'sel') 410 if isinstance(frame, Client): 411 if frame._id is None: 412 return 'sel sel' 413 return 'client %s' % frame.id 414 elif isinstance(frame, basestring): 415 return frame 416 else: 417 return '%s %s' % tuple(map(str, frame)) 418 def dirspec(cls, dir): 419 if isinstance(dir, tuple): 420 dir = ' '.join(dir) 421 return dir 422 423 @property 424 def selected(self): 425 return tuple(self['select'].split(' ')) 426 @selected.setter 427 def selected(self, frame): 428 if not isinstance(frame, basestring) or ' ' not in frame: 429 frame = self.framespec(frame) 430 self['select'] = frame 431 432 @property 433 def selclient(self): 434 for k, v in self.iteritems(): 435 if k == 'select' and 'client' in v: 436 return Client(v.split(' ')[1]) 437 return None 438 @selclient.setter 439 def selclient(self, val): 440 self['select'] = self.framespec(val) 441 442 @property 443 def selcol(self): 444 return Area(self, self.selected[0]) 445 446 @property 447 def index(self): 448 areas = [] 449 for l in (l.split(' ') 450 for l in client.readlines('%s/index' % self.path) 451 if l): 452 if l[0] == '#': 453 m = re.match(r'(?:(\d+):)?(\d+|~)', l[1]) 454 if m.group(2) == '~': 455 area = Area(tag=self, screen=m.group(1), ord=l[1], width=l[2], 456 height=l[3], frames=[]) 457 else: 458 area = Area(tag=self, screen=m.group(1) or 0, 459 height=None, ord=m.group(2), offset=l[2], width=l[3], 460 frames=[]) 461 areas.append(area) 462 i = 0 463 else: 464 area.frames.append( 465 Frame(client=Client(l[1]), area=area, ord=i, 466 offset=l[2], height=l[3])) 467 i += 1 468 return areas 469 470 def delete(self): 471 id = self.id 472 for a in self.index: 473 for f in a.frames: 474 if f.client.tags == id: 475 f.client.kill() 476 else: 477 f.client.tags = '-%s' % id 478 if self == Tag('sel'): 479 Tags.instance.select(Tags.instance.next()) 480 481 def select(self, frame, stack=False): 482 self['select'] = '%s %s' % ( 483 self.framespec(frame), 484 stack and 'stack' or '') 485 486 def send(self, src, dest, stack=False, cmd='send'): 487 if isinstance(src, tuple): 488 src = ' '.join(src) 489 if isinstance(src, Frame): 490 src = src.client 491 if isinstance(src, Client): 492 src = src._id or 'sel' 493 494 if isinstance(dest, tuple): 495 dest = ' '.join(dest) 496 497 self[cmd] = '%s %s' % (src, dest) 498 499 def swap(self, src, dest): 500 self.send(src, dest, cmd='swap') 501 502 def nudge(self, frame, dir, amount=None): 503 frame = self.framespec(frame) 504 self['nudge'] = '%s %s %s' % (frame, dir, str(amount or '')) 505 def grow(self, frame, dir, amount=None): 506 frame = self.framespec(frame) 507 self['grow'] = '%s %s %s' % (frame, dir, str(amount or '')) 508 509 class Color(utf8): 510 def __init__(self, colors): 511 if isinstance(colors, Color): 512 colors = colors.rgb 513 elif isinstance(colors, basestring): 514 match = (re.match(r'^#(..)(..)(..)((?:..)?)$', colors) or 515 re.match(r'^rgba:(..)/(..)/(..)/(..)$', colors)) 516 colors = tuple(int(match.group(group), 16) for group in range(1, 4)) 517 if match.group(4): 518 colors += int(match.group(4), 16), 519 def toint(val): 520 if isinstance(val, float): 521 val = int(255 * val) 522 assert 0 <= val <= 255 523 return val 524 self.rgb = tuple(map(toint, colors)) 525 526 def __getitem__(self, key): 527 if isinstance(key, basestring): 528 key = {'red': 0, 'green': 1, 'blue': 2}[key] 529 return self.rgb[key] 530 531 @property 532 def hex(self): 533 if len(self.rgb) > 3: 534 return 'rgba:%02x/%02x/%02x/%02x' % self.rgb 535 return '#%02x%02x%02x' % self.rgb 536 537 def __unicode__(self): 538 if len(self.rgb) > 3: 539 return 'rgba(%d, %d, %d, %d)' % self.rgb 540 return 'rgb(%d, %d, %d)' % self.rgb 541 def __repr__(self): 542 return 'Color(%s)' % repr(self.rgb) 543 544 class Colors(utf8): 545 def __init__(self, foreground=None, background=None, border=None): 546 vals = foreground, background, border 547 self.vals = tuple(map(Color, vals)) 548 549 def __iter__(self): 550 return iter(self.vals) 551 def __list__(self): 552 return list(self.vals) 553 def __tuple__(self): 554 return self.vals 555 556 @classmethod 557 def from_string(cls, val): 558 return cls(*val.split(' ')) 559 560 def __getitem__(self, key): 561 if isinstance(key, basestring): 562 key = {'foreground': 0, 'background': 1, 'border': 2}[key] 563 return self.vals[key] 564 565 def __unicode__(self): 566 return ' '.join(c.hex for c in self.vals) 567 def __repr__(self): 568 return 'Colors(%s, %s, %s)' % tuple(repr(c.rgb) for c in self.vals) 569 570 class Button(Ctl): 571 sides = { 572 'left': 'lbar', 573 'right': 'rbar', 574 } 575 ctl_types = { 576 'colors': (Colors.from_string, lambda c: str(Colors(*c))), 577 } 578 ctl_open = 'acreate' 579 colors = Dir.ctl_property('colors') 580 label = Dir.ctl_property('label') 581 582 def __init__(self, side, name, colors=None, label=None): 583 super(Button, self).__init__() 584 self.side = side 585 self.name = name 586 self.base_path = self.sides[side] 587 self.ctl_path = '%s/%s' % (self.base_path, self.name) 588 self.ctl_file = None 589 if colors or label: 590 self.create(colors, label) 591 592 def create(self, colors=None, label=None): 593 if not self.ctl_file: 594 self.ctl_file = client.create(self.ctl_path, ORDWR) 595 if colors: 596 self.colors = colors 597 if label: 598 self.label = label 599 600 def remove(self): 601 if self.ctl_file: 602 self.ctl_file.aremove() 603 self.ctl_file = None 604 605 @property 606 def exists(self): 607 return bool(self.file.stat() if self.file else client.stat(self.ctl_path)) 608 609 @classmethod 610 def all(cls, side): 611 return (s.name 612 for s in client.readdir(cls.sides[side]) 613 if s.name != 'sel') 614 @classmethod 615 def map(cls, *args): 616 return Map(cls, *args) 617 618 class Rules(collections.MutableMapping, utf8): 619 620 _items = () 621 def __init__(self, path, rules=None): 622 self.path = path 623 if rules: 624 self.setitems(rules) 625 626 _quotere = re.compile(ur'(\\(.)|/)') 627 @classmethod 628 def quoteslash(cls, str): 629 return cls._quotere.sub(lambda m: m.group(0) if m.group(2) else r'\/', str) 630 631 __get__ = lambda self, obj, cls: self 632 def __set__(self, obj, val): 633 self.setitems(val) 634 635 def __getitem__(self, key): 636 for k, v in self.iteritems(): 637 if k == key: 638 return v 639 raise KeyError() 640 def __setitem__(self, key, val): 641 items = [(k, v) for k, v in self.iteritems() if k != key] 642 items.append((key, val)) 643 self.setitems(items) 644 def __delitem__(self, key): 645 self.setitems((k, v) for k, v in self.iteritems() if k != key) 646 647 def __len__(self): 648 return len(tuple(self.iteritems())) 649 def __iter__(self): 650 return (k for k, v in self.iteritems()) 651 def __list__(self): 652 return list(iter(self)) 653 def __tuple__(self): 654 return tuple(iter(self)) 655 656 def append(self, item): 657 self.setitems(self + (item,)) 658 def __add__(self, items): 659 return tuple(self.iteritems()) + tuple(items) 660 661 def rewrite(self): 662 client.awrite(self.path, unicode(self)) 663 def setitems(self, items): 664 self._items = [(k, v if isinstance(v, Rule) else Rule(self, k, v)) 665 for (k, v) in items] 666 self.rewrite() 667 668 def __unicode__(self): 669 return u''.join(unicode(value) for (key, value) in self.iteritems()) or u'\n' 670 671 def iteritems(self): 672 return iter(self._items) 673 def items(self): 674 return list(self._items()) 675 676 class Rule(collections.MutableMapping, utf8): 677 _items = () 678 parent = None 679 680 @classmethod 681 def quotekey(cls, key): 682 if key.endswith('_'): 683 key = key[:-1] 684 return key.replace('_', '-') 685 @classmethod 686 def quotevalue(cls, val): 687 if val is True: return "on" 688 if val is False: return "off" 689 if val in (Toggle, Always, Never): 690 return unicode(val).lower() 691 return tounicode(val) 692 693 def __get__(self, obj, cls): 694 return self 695 def __set__(self, obj, val): 696 self.setitems(val) 697 698 def __init__(self, parent, key, items={}): 699 self.key = key 700 self._items = [] 701 self.setitems(items.iteritems() if isinstance(items, dict) else items) 702 self.parent = parent 703 704 def __getitem__(self, key): 705 for k, v in reversed(self._items): 706 if k == key: 707 return v 708 raise KeyError() 709 710 def __setitem__(self, key, val): 711 items = [(k, v) for k, v in self.iteritems() if k != key] 712 items.append((key, val)) 713 self.setitems(items) 714 715 def __delitem__(self, key): 716 self.setitems([(k, v) for k, v in self.iteritems() if k != key]) 717 718 def __len__(self): 719 return len(self._items) 720 def __iter__(self): 721 return iter(self._items) 722 def __list__(self): 723 return list(iter(self)) 724 def __tuple__(self): 725 return tuple(iter(self)) 726 727 def append(self, item): 728 self.setitems(self + (item,)) 729 def __add__(self, items): 730 return tuple(self.iteritems()) + tuple(items) 731 732 def setitems(self, items): 733 items = list(items) 734 assert not any('=' in key or 735 spacere.search(self.quotekey(key)) or 736 spacere.search(self.quotevalue(val)) for (key, val) in items) 737 self._items = items 738 if self.parent: 739 self.parent.rewrite() 740 741 def __unicode__(self): 742 return u'/%s/ %s\n' % ( 743 Rules.quoteslash(self.key), 744 u' '.join(u'%s=%s' % (self.quotekey(k), self.quotevalue(v)) 745 for (k, v) in self.iteritems())) 746 747 def iteritems(self): 748 return iter(self._items) 749 def items(self): 750 return list(self._items) 751 752 753 @apply 754 class wmii(Ctl): 755 ctl_path = '/ctl' 756 ctl_types = { 757 'normcolors': (Colors.from_string, lambda c: str(Colors(*c))), 758 'focuscolors': (Colors.from_string, lambda c: str(Colors(*c))), 759 'border': (int, str), 760 } 761 762 clients = Client.map() 763 tags = Tag.map() 764 lbuttons = Button.map('left') 765 rbuttons = Button.map('right') 766 767 rules = Rules('/rules') 768 769 class Tags(object): 770 PREV = [] 771 NEXT = [] 772 773 def __init__(self, normcol=None, focuscol=None): 774 self.ignore = set() 775 self.tags = {} 776 self.sel = None 777 self.normcol = normcol 778 self.focuscol = focuscol 779 self.lastselect = datetime.now() 780 for t in wmii.tags: 781 self.add(t) 782 for b in wmii.lbuttons.itervalues(): 783 if b.name not in self.tags: 784 b.remove() 785 self.focus(Tag('sel').id) 786 787 self.mru = [self.sel.id] 788 self.idx = -1 789 Tags.instance = self 790 791 def add(self, tag): 792 self.tags[tag] = Tag(tag) 793 self.tags[tag].button = Button('left', tag, self.normcol or wmii.cache['normcolors'], tag) 794 def delete(self, tag): 795 self.tags.pop(tag).button.remove() 796 797 def focus(self, tag): 798 self.sel = self.tags[tag] 799 self.sel.button.colors = self.focuscol or wmii.cache['focuscolors'] 800 def unfocus(self, tag): 801 self.tags[tag].button.colors = self.normcol or wmii.cache['normcolors'] 802 803 def set_urgent(self, tag, urgent=True): 804 self.tags[tag].button.label = urgent and '*' + tag or tag 805 806 def next(self, reverse=False): 807 tags = [t for t in wmii.tags if t not in self.ignore] 808 tags.append(tags[0]) 809 if reverse: 810 tags.reverse() 811 for i in range(0, len(tags)): 812 if tags[i] == self.sel.id: 813 return tags[i+1] 814 return self.sel 815 816 def select(self, tag, take_client=None): 817 def goto(tag): 818 if take_client: 819 # Make a new instance in case this is Client('sel'), 820 # which would cause problems given 'sel' changes in the 821 # process. 822 client = Client(take_client.id) 823 824 sel = Tag('sel').id 825 client.tags = '+%s' % tag 826 wmii['view'] = tag 827 if tag != sel: 828 client.tags = '-%s' % sel 829 else: 830 wmii['view'] = tag 831 832 if tag is self.PREV: 833 if self.sel.id not in self.ignore: 834 self.idx -= 1 835 elif tag is self.NEXT: 836 self.idx += 1 837 else: 838 if isinstance(tag, Tag): 839 tag = tag.id 840 goto(tag) 841 842 if tag not in self.ignore: 843 if self.idx < -1: 844 self.mru = self.mru[:self.idx + 1] 845 self.idx = -1 846 if self.mru and datetime.now() - self.lastselect < timedelta(seconds=.5): 847 self.mru[self.idx] = tag 848 elif tag != self.mru[-1]: 849 self.mru.append(tag) 850 self.mru = self.mru[-10:] 851 self.lastselect = datetime.now() 852 return 853 854 self.idx = constrain(-len(self.mru), -1, self.idx) 855 goto(self.mru[self.idx]) 856 857 # vim:se sts=4 sw=4 et: