ubuntuusers.de

Metacity tiling Python

Status: Ungelöst | Ubuntu-Version: Ubuntu 8.10 (Intrepid Ibex)
Antworten |

Romario

Avatar von Romario

Anmeldungsdatum:
7. Februar 2008

Beiträge: 323

Wohnort: Köln

snafu1 schrieb:

Habe dein Skript jetzt mal getestet. Die Probleme mit den Panels bestehen da leider auch.

Welches Problem genau? Dass die Fenster unter den Panels liegen?
Du kannst die Größe der Panels ja abfragen und das bei der Positionierung der übrigen Fenster berücksichtigen (also ist der Ursprung nicht 0,0 sondern 0, <Höhe des oberen Panels> usw.).

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Ja, aber die Zahlen stimmen nicht so ganz. Selbes Problem wie bei Screen Height und Width. Man müsste wohl mal rausfinden ob die zurückgegebenen Werte und die tatsächlichen Werte in einem Verhältnis zueinander stehen. Aber da hab ich jetzt keine Lust drauf. ^^

zhocchao

(Themenstarter)

Anmeldungsdatum:
5. Mai 2008

Beiträge: 65

Das Python script bewirkt bei mir nichts. Auch keine Fehlermeldung. Das Perl script ändert die Fenster auf allen Workspaces, so das es passen würde, wären sie alle auf einem. Das Panel verdeckt nichts und wird auch nicht verdeckt. Die höhe vom Terminal wird aber nicht verändert.

Romario

Avatar von Romario

Anmeldungsdatum:
7. Februar 2008

Beiträge: 323

Wohnort: Köln

snafu1 schrieb:

Ja, aber die Zahlen stimmen nicht so ganz. Selbes Problem wie bei Screen Height und Width. Man müsste wohl mal rausfinden ob die zurückgegebenen Werte und die tatsächlichen Werte in einem Verhältnis zueinander stehen. Aber da hab ich jetzt keine Lust drauf. ^^

Kann auch an der Fensterdekoration liegen. Folgende Funktionen würde ich mir mal ansehen (bzw. entsprechende Funktionen innerhalb der Python-Bindings)

1
2
(x, y, width, height) = $window->get_client_window_geometry
(xp, yp, widthp, heightp) = $window->get_geometry 

zhocchao schrieb:

Das Perl script ändert die Fenster auf allen Workspaces, so das es passen würde, wären sie alle auf einem. Das Panel verdeckt nichts und wird auch nicht verdeckt. Die höhe vom Terminal wird aber nicht verändert.

Mehr wollte ich damit auch nicht erreichen. Ich habe leider keine Zeit richtig an diesem Projekt mitzuarbeiten. Es war lediglich als Ansatz gedacht.

zhocchao

(Themenstarter)

Anmeldungsdatum:
5. Mai 2008

Beiträge: 65

Romario schrieb:

Mehr wollte ich damit auch nicht erreichen. Ich habe leider keine Zeit richtig an diesem Projekt mitzuarbeiten. Es war lediglich als Ansatz gedacht.

Alles klar. Ich meinte das auch nur wegen:

Romario schrieb:

snafu1 schrieb:

Habe dein Skript jetzt mal getestet. Die Probleme mit den Panels bestehen da leider auch.

Welches Problem genau? Dass die Fenster unter den Panels liegen?
Du kannst die Größe der Panels ja abfragen und das bei der Positionierung der übrigen Fenster berücksichtigen (also ist der Ursprung nicht 0,0 sondern 0, <Höhe des oberen Panels> usw.).

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

zhocchao schrieb:

Das Python script bewirkt bei mir nichts. Auch keine Fehlermeldung. Das Perl script ändert die Fenster auf allen Workspaces, so das es passen würde, wären sie alle auf einem. Das Panel verdeckt nichts und wird auch nicht verdeckt. Die höhe vom Terminal wird aber nicht verändert.

Sehr merkwürdig. Ich frag mich ob das am Windowmanager liegen könnte (bei mir wie gesagt Xfce). Müsste man mal genauer untersuchen.

Wie gesag: Wir könne gerne auf wnck umsteigen. Der Code ist IMHO wesentlich lesbarer, so dass vielleicht auch zhocchao mehr zum Projekt beitragen kann.

Das Perl-Skript auf Python übertragen:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
#!/usr/bin/env python

import wnck

screen = wnck.screen_get_default()
screen.force_update()

for win in screen.get_windows():
    if not 'Panel' in win.get_name():
        print win.get_name()
        win.unmaximize()
        win.set_geometry('current', 'width', 0, 0, 320, 280)

Da ich die Python-Bindings nicht in den Repos gefunden habe, hier eine kleine Anleitung zur Installation:

- Das Paket zeroinstall-injector installieren

- Dann im Anwendungsmenü den neuen Eintrag "Add New Program" auswählen (weiß nicht wo es sich unter Gnome befindet, bei mir unter "Andere")

- Folgende Adresse als URI kopieren: http://rox4debian.berlios.de/0install/Python-Wnck.xml

- Und der Rest dürfte selbsterklärend sein

zhocchao

(Themenstarter)

Anmeldungsdatum:
5. Mai 2008

Beiträge: 65

Hab den Fehler gefunden. Ich hatte zuviele Fenster offen (über die verschiedenen Arbeitsflächen verteilt). Mit nur zwei offenen Fenstern hab ich dasselbe Ergebnis wie beim Perl script. Panel stellen bei mir kein Problem dar.

wnck ist wirklich mal einfacher zu verstehen. Jedoch sollten die Fenster nicht quer über alle Desktops verändert werden. Es erscheint x-nautilus-desktop in der Liste.

(so langsam gehen bei mir die Lichter aus...)

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

zhocchao schrieb:

Hab den Fehler gefunden. Ich hatte zuviele Fenster offen (über die verschiedenen Arbeitsflächen verteilt). Mit nur zwei offenen Fenstern hab ich dasselbe Ergebnis wie beim Perl script.

Ja, das muss natürlich alles noch verbessert werden. Eigentlich müsste die Funktion tile() beliebig viele Fenster aufnehmen und dann nach einem vorgegebenen Schema entscheiden, was es damit tut. Denkbar wären bis zu drei Fenster nebeneinander, bei vier Fenstern quadratische Anordnugn, bei fünf keine Ahnung, bei sechs 3 oben / 3 unten usw. Das ginge dann wohl auch in die Richtung, die du dir vorgestellt hast. Die Vorgaben könnten natürlich später vom Benutzer angepasst werden.

Panel stellen bei mir kein Problem dar.

Vielleicht regelt ja nur Xfce das so komisch. Ich würd ja mal sagen, dass Gnome ohnehin ausgereifter ist. Füllen die beiden Fenster denn korrekt den Bildschirmbereich oder gibt es Überlappungen?

wnck ist wirklich mal einfacher zu verstehen. [...] Es erscheint x-nautilus-desktop in der Liste.

Ja, denn bei wnck wird eben nur alles rausgeschmissen, was nicht "Panel" im Namen hat. Bei mir erscheint auch "Arbeitsoberfläche" in der Liste. Die Xlib-Variante hingegen prüft ja (völlig unabhängig von Namen) die Eigenschaften des Elements. Also ob es wirklich ein Fenster ist. Das finde ich auch wesentlich eleganter. Allerdings habe ich bisher nicht rausfinden können, wie man das mit wnck macht. Das ist halt manchmal der Nachteil an den Sachen, die auf dem ersten Blick einfach erscheinen: Wenn's ins Detail geht, steht man unter Umständen (muss ja nicht) dumm da...

Ich habe jetzt auch mal zum Testen Metacity als Fenstermanager genommen, aber die Xfce-Oberfläche behalten: Ergebnis war ein wieder anderes Verhalten. Keine Überlappung der Panels aber dafür haben sich die Fenster an den Seiten stark überlappt. Vielleicht ist es tatsächlich besser, das so zu machen, wie Fred vorgeschlagen hat. Also nicht hinter dem Rücken des WM's.

fred.reichbier schrieb:

Besser wäre es vielleicht, die _NET_MOVERESIZE_WINDOW - ClientMessage zu nutzen. Ich hab dafür hier was 😀

Ich habe versucht, das zu übertragen. Leider passiert aber rein gar nichts. Auch keine Fehlermeldung.

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
#!/usr/bin/env python

from optparse import OptionParser
import sys

from Xlib.display import Display
from Xlib.protocol.event import ClientMessage
from Xlib.X import (
    AnyPropertyType,
    SubstructureNotifyMask,
    SubstructureRedirectMask)


class WinControl(object):

    def __init__(self):
        self.display = Display()
        self.screen = self.display.screen()
        self.root = self.screen.root
        self.CLIENTLIST = self.display.intern_atom('_NET_CLIENT_LIST')
        self.MOVERESIZE = self.display.intern_atom('_NET_MOVERESIZE_WINDOW')
        self.NETNAME = self.display.intern_atom('_NET_WM_NAME')
        self.NORMAL = self.display.intern_atom('_NET_WM_WINDOW_TYPE_NORMAL')
        self.WINTYPE = self.display.intern_atom('_NET_WM_WINDOW_TYPE')

    def get_xids(self, only_xids=False, wins=False, max=-1):
        """ Return all xids from clientlist
        
            xids : return only xids (no names)
            wins : Return only window xids
            max  :  Number of maximal returned xids, -1 means no maximum
        """ 
        xids = self.root.get_full_property(
            self.CLIENTLIST,
            AnyPropertyType).value
        if wins:
            xids = [xid for xid in xids if self.iswindow(xid)]
        if max == -1:
            max = len(xids)
        for i in xrange(max):
            xid = xids[i]
            if only_xids:
                yield int(xid)
            else:
                yield int(xid), self.get_name(xid)
                
    def iswindow(self, xid):
        window = self.get_window(xid)
        wintype = window.get_full_property(self.WINTYPE, AnyPropertyType).value
        return self.NORMAL in wintype

    def get_name(self, xid):
        window = self.get_window(xid)
        netname = window.get_full_property(self.NETNAME, AnyPropertyType).value
        return window.get_wm_name() or netname

    def get_window(self, xid):
        return self.display.create_resource_object('window', xid)

    def tile(self, left, right):
        """ Tile two windows verticale using their xids
        """
        w, h = self.screen.width_in_pixels, self.screen.height_in_pixels
        win_left, win_right = self.get_window(left), self.get_window(right)
        self.moveresize(win_left, x=0, y=0, width=w/2, height=h)
        self.moveresize(win_right, x=w/2, y=0, width=w/2, height=h)
        #self.display.flush()

    def moveresize(self, window, gravity=0, x=-1, y=-1, width=-1, height=-1):
        """ This is mostly adapted from the pywmctrl project
            See: http://hg.lophus.org/pywmctrl/file/tip/pywmctrl.py
            
            Negative values mean zero
        """
        flags = gravity
        if x > -1:
            flags |= 1 << 8
        else:
            x = 0
        if y > -1:
            flags |= 1 << 9
        else:
            y = 0
        if width > 0:
            flags |= 1 << 10
        else:
            width = 0
        if height > 0:
            flags |= 1 << 11
        else:
            height = 0
        data = [flags, x, y, width, height]
        fillup = [0] * (5 - len(data))
        moveresize = ClientMessage(
            window=window,
            client_type=self.MOVERESIZE,
            data=(32, data + fillup))
        mask = (SubstructureRedirectMask|SubstructureNotifyMask)
        self.root.send_event(moveresize, event_mask=mask)
        self.display.sync()



def main():
    """Get all open window names and their ids in xprop style (hexadecimal)
    
    Option '-t' / '--tile':
    Tile first two windows of clientlist instead of showing id's
    """
    parser = OptionParser(
        'Usage: %prog [-t|--tile] [-h|--help]\n' \
        'Get all open window names and their ids in xprop style (hexadecimal)')

    parser.add_option(
        '-t',
        '--tile',
        dest='tile',
        action='store_true',
        help="tile first two windows of clientlist instead of showing id's")

    (options, args) = parser.parse_args()

    wc = WinControl()

    if options.tile:
        try:
            left, right = wc.get_xids(only_xids=True, wins=True, max=2)
        except IndexError:
            print >> sys.stderr, 'Could not tile: Found less than two windows'
            sys.exit(1)
        wc.tile(left, right)

    else:
        for xid, name in wc.get_xids(wins=True):
            print hex(xid), name


if __name__ == '__main__':
    main()

zhocchao

(Themenstarter)

Anmeldungsdatum:
5. Mai 2008

Beiträge: 65

das letzte script hat die Fenster nicht überlappt. Dieses jetzt aber schon. Dafür hat dieses die maximierten ignoriert, was ich prima finde. Beim zweiten Auführen hats aber nur das Terminal bewegt und gedit ignoriert.

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Ich habe den jetzigen Stand auf wnck übertragen. Funktioniert das Tiling? Fenster bitte nicht auf Vollbild oder minimiert machen.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
#!/usr/bin/env python

""" Tile your open windows

    Based on python-wnck. To download the module visit:
    http://rox4debian.berlios.de/0install/Python-Wnck.xml
"""

from optparse import OptionParser
import sys

import wnck


class Screen(object):
    """ Provide access to open windows and screen size
    """
    def __init__(self):
        self.screen = wnck.screen_get_default()
        self.screen.force_update()

    def get_windows(self, max=-1):
        """ Check client list for 'real' windows

            Yield ALL windows as tuples (name, winobj)
            Setting max will limit that 

            NOTE: winobj is a wnck.Window object. See perl-doc for details:
            http://gtk2-perl.sourceforge.net/doc/pod/Gnome2/Wnck/Window.html
        """
        windows = [win for win in self.screen.get_windows()
                   if self.iswindow(win)]
        if max == -1:
            max = len(windows)
        for i in xrange(max):
            win = windows[i]
            if self.iswindow(win):
                yield win.get_name(), win

    def iswindow(self, win):
        return win.get_window_type() == wnck.WindowType(0)

    def get_screen_size(self):
        return self.screen.get_width(), self.screen.get_height()


def tile():
    """ TODO: Tile window objects according to specified settings
        Current status only tiles first two windows from client list verticale
    """
    screen = Screen()
    left, right = screen.get_windows(max=2)
    width, height = screen.get_screen_size()
    left[1].set_geometry('current', 'width', 0, 0, width/2, height)
    right[1].set_geometry('current', 'width', width/2, 0, width/2, height)
    return "Tiled '%s' and '%s'" % (left[0], right[0]) 


def main():
    """ Get all open window names and their ids in xprop style (hexadecimal)
    
        Option '-t' / '--tile':
        Tile first two windows of clientlist instead of showing id's
    """
    parser = OptionParser(
        'Usage: %prog [-t|--tile] [-h|--help]\n' \
        'Get all open window names and their ids in xprop style (hexadecimal)')

    parser.add_option(
        '-t',
        '--tile',
        dest='tile',
        action='store_true',
        help="tile first two windows of clientlist instead of showing id's")

    (options, args) = parser.parse_args()

    if options.tile:
        try:
            print tile()
        except IndexError:
            print >> sys.stderr, 'Could not tile: Found less than two windows'
            sys.exit(1)

    else:
        screen = Screen()
        for name, winobj in screen.get_windows():
            print hex(int(winobj.get_xid())), name


if __name__ == '__main__':
    main()

Nun auch unter Gnome und KDE getestet. Ich würde die Xlib-Version bevorzugen. Unter KDE läuft sie bei mir am besten. Aber das ist alles nicht wirklich optimal. Besonders wenn man das Skript zweimal hintereinander aufruft, zeigen sich unterschiedliche Verhaltensweisen. Ich frage mich, ob es im Moment überhaupt soviel Sinn macht, das weiterzuentwickeln. Vielleicht gibt es aus genau diesem Grund noch kein Tool für's Tiling.

Das ist echt krass. Ich habe jetzt die tatsächliche Vollbildgröße ermittelt und diese bei configure eingetragen (also als width die Hälfte). Führe ich den Befehl aus dem Interpreter aus, gibt er dem Fenster nach dem flush() die korrekten Maße. Trage ich es stattdessen ins Skript ein, wird das Fenster viel breiter. Keine Ahnung, woran das liegt, aber an mir ja wohl nicht. Solange sich da kein Workaround findet, kann ich mich wahrscheinlich dumm und dusselig probieren.

zhocchao

(Themenstarter)

Anmeldungsdatum:
5. Mai 2008

Beiträge: 65

zero install liefert mir stöndig time outs.

Ist es eigentlich nötig, das Rad zum x-ten Mal zu erfinden? wmctrl kann doch eigentlich alles (?). Wäre doch einfacher ein kleines drumherum zu schreiben. Auch wenns nicht so schön ist.

snafu1

Avatar von snafu1

Anmeldungsdatum:
5. September 2007

Beiträge: 2133

Wohnort: Gelsenkirchen

Das stimmt eigentlich.

Dann wäre meine Idee, zuerst einen kleinen Wrapper zu schreiben (vornehmlich für die benötigten Optionen), der - wie ein Wrapper das so macht - die Ausgabe von wmctrl in halbwegs brauchbare Python-Objekte umwandelt. Das ganze sollte man meiner Meinung nach in ein eigenes Modul packen. Denn vielleicht möchte es beizeiten ja unabhängig vom Tiler erweitert werden... Und dieses Modul verwendet dann unser Tiler für seine Aufgaben. Was hälst du davon?

Ich habe hier schon mal die beiden ersten Funktionen gebastelt:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
from subprocess import PIPE, Popen

def get_windows():
    """ Return a list of all open windows

        Use 9-tuples for each window 
        (id, desktop, pid, x, y, width, height, client, title)

        id      : window identity (hexadecimal)
        desktop : desktop number (-1 means sticky window, e.g. panels)
        pid     : window pid
        x       : x-offset
        y       : y-offset
        width   : window width
        height  : window height
        client  : client machine name
        title   : window title

        NOTE: All values are strings # TODO
    """
    return [tuple(line.split(None, 8)) for line in _wclines('-lpG') if line]


def get_workarea():
    """ Return workarea geometry of current desktop
        (this means the area without panels)

        Use 2-tuple (x, y)
    """
    for line in _wclines('-d'):
        if '*' in line: # '*' marks current desktop
            break
    geometry = line.split(None, 9)[8]
    return tuple(map(int, geometry.split('x')))


def _wclines(arg):
    """ Run wmctrl with specified arg and return output lines in a list
    """ 
    return Popen(['wmctrl', arg], stdout=PIPE).communicate()[0].split('\n')
Antworten |