Linux WindowManager module

Share your advanced PureBasic knowledge/code with the community.
infratec
Always Here
Always Here
Posts: 7618
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Linux WindowManager module

Post by infratec »

A native PB implementtion of the wmctrl functions.

Save it as wm.pbi:

Code: Select all

CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
CompilerEndIf

DeclareModule wm
  Structure window_structure
    pid.i
    desktop.i
    client_machine$
    title$
  EndStructure
  
  Declare.i open_display(Display$="")
  Declare.i list_windows(*disp, List WindowList.window_structure())
  Declare.i list_child_windows(*disp, win)
  Declare.i get_active_window(*disp)
  Declare.s get_window_title(*disp, win)
  Declare.i activate_window(*disp, win)
  Declare.i get_window_by_title(*disp, title$)
  Declare.i window_move_resize(*disp, win, x=-1, y=-1, w=-1, h=-1)
  Declare.i set_parent(*disp, w, parent, x, y)
  Declare.i close_window(*disp, win)
  Declare.i close_display(*disp)
  
EndDeclareModule


Module wm
  
  EnableExplicit
  
  #XA_ARC                 = 3
  #XA_ATOM                = 4
  #XA_BITMAP              = 5
  #XA_CAP_HEIGHT          = 66
  #XA_CARDINAL            = 6
  #XA_COLORMAP            = 7
  #XA_COPYRIGHT           = 61
  #XA_CURSOR              = 8
  #XA_CUT_BUFFER0         = 9
  #XA_CUT_BUFFER1         = 10
  #XA_CUT_BUFFER2         = 11
  #XA_CUT_BUFFER3         = 12
  #XA_CUT_BUFFER4         = 13
  #XA_CUT_BUFFER5         = 14
  #XA_CUT_BUFFER6         = 15
  #XA_CUT_BUFFER7         = 16
  #XA_DRAWABLE            = 17
  #XA_END_SPACE           = 46
  #XA_FAMILY_NAME         = 64
  #XA_FONT                = 18
  #XA_FONT_NAME           = 63
  #XA_FULL_NAME           = 65
  #XA_INTEGER             = 19
  #XA_ITALIC_ANGLE        = 55
  #XA_LAST_PREDEFINED     = 68
  #XA_MAX_SPACE           = 45
  #XA_MIN_SPACE           = 43
  #XA_NORM_SPACE          = 44
  #XA_NOTICE              = 62
  #XA_PIXMAP              = 20
  #XA_POINT               = 21
  #XA_POINT_SIZE          = 59
  #XA_PRIMARY             = 1
  #XA_QUAD_WIDTH          = 57
  #XA_RECTANGLE           = 22
  #XA_RESOLUTION          = 60
  #XA_RESOURCE_MANAGER    = 23
  #XA_RGB_BEST_MAP        = 25
  #XA_RGB_BLUE_MAP        = 26
  #XA_RGB_COLOR_MAP       = 24
  #XA_RGB_DEFAULT_MAP     = 27
  #XA_RGB_GRAY_MAP        = 28
  #XA_RGB_GREEN_MAP       = 29
  #XA_RGB_RED_MAP         = 30
  #XA_SECONDARY           = 2
  #XA_STRIKEOUT_ASCENT    = 53
  #XA_STRIKEOUT_DESCENT   = 54
  #XA_STRING              = 31
  #XA_SUBSCRIPT_X         = 49
  #XA_SUBSCRIPT_Y         = 50
  #XA_SUPERSCRIPT_X       = 47
  #XA_SUPERSCRIPT_Y       = 48
  #XA_UNDERLINE_POSITION  = 51
  #XA_UNDERLINE_THICKNESS = 52
  #XA_VISUALID            = 32
  #XA_WEIGHT              = 58
  #XA_WINDOW              = 33
  #XA_WM_CLASS            = 67
  #XA_WM_CLIENT_MACHINE   = 36
  #XA_WM_COMMAND          = 34
  #XA_WM_HINTS            = 35
  #XA_WM_ICON_NAME        = 37
  #XA_WM_ICON_SIZE        = 38
  #XA_WM_NAME             = 39
  #XA_WM_NORMAL_HINTS     = 40
  #XA_WM_SIZE_HINTS       = 41
  #XA_WM_TRANSIENT_FOR    = 68
  #XA_WM_ZOOM_HINTS       = 42
  #XA_X_HEIGHT            = 56
  
  
  #MAX_PROPERTY_VALUE_LEN = 2048
  
  #Success = 0
  
  #EXIT_SUCCESS = 0
  #EXIT_FAILURE = 1
  
  #ClientMessage          = 33
  #Expose                 = 12
  
  
  #KeyPressMask             = 1 << 0
  #KeyReleaseMask           = 1 << 1
  #Button3MotionMask	      = 1 << 10
  #Button4MotionMask	      = 1 << 11
  #Button5MotionMask	      = 1 << 12
  #ButtonMotionMask         = 1 << 13
  #KeymapStateMask          = 1 << 14
  #ExposureMask             = 1 << 15
  #VisibilityChangeMask     = 1 << 16
  #StructureNotifyMask      = 1 << 17
  #ResizeRedirectMask       = 1 << 18
  #SubstructureNotifyMask   = 1 << 19
  #ButtonPressMask          = 1 << 2
  #SubstructureRedirectMask = 1 << 20
  #FocusChangeMask          = 1 << 21
  #PropertyChangeMask       = 1 << 22
  #ColormapChangeMask       = 1 << 23
  #ButtonReleaseMask        = 1 << 3
  #EnterWindowMask          = 1 << 4
  #LeaveWindowMask          = 1 << 5
  #PointerMotionMask        = 1 << 6
  #PointerMotionHintMask    = 1 << 7
  #Button1MotionMask        = 1 << 8
  #Button2MotionMask        = 1 << 9
  
  
  Structure ArrayOfInteger
    i.i[0]
  EndStructure
  
  Structure XAnyEvent Align #PB_Structure_AlignC
    type.i
    serial.i      ;	# of last request processed by server
    send_event.i  ;	true if this came from a SendEvent request
    *display      ;	Display the event was read from
    window.i
  EndStructure
  
  Structure Data_Structure Align #PB_Structure_AlignC
    StructureUnion
      b.a[20]
      s.u[20]
      i.i[5]
    EndStructureUnion
  EndStructure
  
  Structure XClientMessageEvent Align #PB_Structure_AlignC
    type.i              ; ClientMessage
    serial.i            ; # of last request processed by server
    send_event.i        ; true if this came from a SendEvent request
    *display            ; Display the event was read from
    window.i
    message_type.i
    format.i
    data_.Data_Structure
  EndStructure
  
  Structure XExposeEvent Align #PB_Structure_AlignC
    type.i              ; Expose
    serial.i            ; # of last request processed by server
    send_event.i        ; true if this came from a SendEvent request
    *display            ; Display the event was read from
    window.i
    x.i
    y.i
    width.i
    height.i
    count.i             ; if nonzero, at least this many more
  EndStructure
  
  
  Structure XEvent Align #PB_Structure_AlignC
    StructureUnion
      type.i
      xany.XanyEvent
      xexpose.XExposeEvent
      xclient.XClientMessageEvent
      pad.i[24]
    EndStructureUnion
  EndStructure
  
  
  
  
  
  Macro DefaultRootWindow(disp)
    XDefaultRootWindow(disp)
  EndMacro
  
  
  ImportC "/usr/lib/x86_64-linux-gnu/libX11.so"
    XOpenDisplay.i(display.p-utf8)
    XCloseDisplay.i(*display)
    XInternAtom.i(*display, atom_name.p-utf8, only_if_exists)
    XFree.i(*Data)
    
    XDefaultRootWindow.i(*display)
    XGetWindowProperty.i(*display, w, property, long_offset, long_length, delete, req_type, *actual_type_return, *actual_format_return, *nitems_return, *bytes_after_return, *prop_return)
    
    XSendEvent.i(*display, w, propagate, event_mask, *event_send)
    
    XMoveWindow.i(*display, w, x, y)
    XResizeWindow.i(*display, w, width, height)
    XMoveResizeWindow.i(*display, w, x, y, width, height)
    
    XReparentWindow.i(*display, w, parent, x, y)
    XUnmapWindow.i(*display, w)
    XMapWindow.i(*display, w)
    
    XMapRaised(*display, w)
    
    XSync.i(*display, discard)
    XFlush(*display)
    XQueryTree.i(*display, w, *root_return, *parent_return, *children_return, *nchildren_return)
  EndImport
  
  
  
  Procedure.i client_msg(*disp, win, msg$, data0, data1, data2, data3, data4)
    
    Protected event.XEvent, mask.i, Result.i
    
    
    mask = #SubstructureRedirectMask | #SubstructureNotifyMask
    
    event\xclient\type = #ClientMessage
    event\xclient\serial = 0
    event\xclient\send_event = #True
    event\xclient\message_type = XInternAtom(*disp, msg$, #False)
    event\xclient\window = win
    event\xclient\format = 32
    event\xclient\data_\i[0] = data0
    event\xclient\data_\i[1] = data1
    event\xclient\data_\i[2] = data2
    event\xclient\data_\i[3] = data3
    event\xclient\data_\i[4] = data4
    
    If XSendEvent(*disp, DefaultRootWindow(*disp), #False, mask, @event)
      XFlush(*disp)
      Result =  #EXIT_SUCCESS
    Else
      Debug "Cannot send " + msg$ + " event"
      Result = #EXIT_FAILURE
    EndIf
    
    ProcedureReturn Result
    
  EndProcedure
  
  
  
  
  Procedure.i get_property(*disp, win, xa_prop_type, prop_name$, *size.Long)
    
    Protected xa_prop_name, xa_ret_type
    Protected ret_format, ret_nitems, ret_bytes_after, tmp_size
    Protected *ret_prop, *ret
    
    
    xa_prop_name = XInternAtom(*disp, prop_name$, #False)
    
    ; MAX_PROPERTY_VALUE_LEN / 4 explanation (XGetWindowProperty manpage):
    ; 
    ; long_length = Specifies the length in 32-bit multiples of the
    ;               Data To be retrieved.
    ;
    If XGetWindowProperty(*disp, win, xa_prop_name, 0, #MAX_PROPERTY_VALUE_LEN / 4, #False, xa_prop_type, @xa_ret_type, @ret_format, @ret_nitems, @ret_bytes_after, @*ret_prop) = #Success
      
      If xa_ret_type = xa_prop_type
        
        ; null terminate the result To make string handling easier (if the result is a string)
        ;Debug "ret_format: " + Str(ret_format) + "  ret_nitems: " + Str(ret_nitems)
        tmp_size = (ret_format / (32 / SizeOf(Integer))) * ret_nitems
        
        ;ShowMemoryViewer(*ret_prop, tmp_size)
        
        *ret = AllocateMemory(tmp_size + 1)
        CopyMemory(*ret_prop, *ret, tmp_size)
        
        If *size
          *size\l = tmp_size
        EndIf
        
      EndIf
      XFree(*ret_prop)
    EndIf
    
    ProcedureReturn *ret
    
  EndProcedure
  
  
  
  
  Procedure.i wm_supports(*disp, prop$)
    
    Protected.i xa_prop, size, i, Result
    Protected *List.ArrayOfInteger
    
    
    xa_prop = XInternAtom(*disp, prop$, #False)
    
    *List = get_property(*disp, DefaultRootWindow(*disp), #XA_ATOM, "_NET_SUPPORTED", @size)
    If *List
      
      For i = 0 To (size / SizeOf(Integer)) - 1
        If *List\i[i] = xa_prop
          Result = #True
          Break
        EndIf
      Next i
      
      FreeMemory(*List)
      
    Else
      Debug "Cannot get _NET_SUPPORTED property."
    EndIf
    
    ProcedureReturn Result
    
  EndProcedure
  
  
  
  
  Procedure.s get_window_title(*disp, win)
    
    Protected title_utf8$
    Protected *wm_name
    Protected *net_wm_name
    
    
    *net_wm_name = get_property(*disp, win, XInternAtom(*disp, "UTF8_STRING", #False), "_NET_WM_NAME", #Null)
    If *net_wm_name
      title_utf8$ = PeekS(*net_wm_name, -1, #PB_UTF8)
      FreeMemory(*net_wm_name)
    Else
      *wm_name = get_property(*disp, win, #XA_STRING, "WM_NAME", #Null)
      If *wm_name
        title_utf8$ = PeekS(*wm_name, -1, #PB_UTF8)
        FreeMemory(*wm_name)
      EndIf
    EndIf
    
    ProcedureReturn title_utf8$
    
  EndProcedure
  
  
  
  Procedure.i get_active_window(*disp)
    
    Protected *prop
    Protected size
    Protected ret
    
    *prop = get_property(*disp, DefaultRootWindow(*disp), #XA_WINDOW, "_NET_ACTIVE_WINDOW", @size)
    If *prop
      ret = PeekI(*prop)
      FreeMemory(*prop)
    EndIf
    
    ProcedureReturn ret
    
  EndProcedure
  
  
  
  
  Procedure.i activate_window(*disp, win)
    client_msg(*disp, win, "_NET_ACTIVE_WINDOW", 0, 0, 0, 0, 0)
    XMapRaised(*disp, win)
    
    ProcedureReturn #True
    
  EndProcedure
  
  
  
  
  Procedure.i close_window(*disp, win)
    ProcedureReturn client_msg(*disp, win, "_NET_CLOSE_WINDOW", 0, 0, 0, 0, 0)
  EndProcedure
  
  
  
  Procedure.i window_move_resize(*disp, win, x=-1, y=-1, w=-1, h=-1)
    
    Protected.i Result, grflags
    
    
    If x <> -1 : grflags | (1 << 8) : EndIf
    If y <> -1 : grflags | (1 << 9) : EndIf
    If w <> -1 : grflags | (1 << 10) : EndIf
    If h <> -1 : grflags | (1 << 11) : EndIf
    ;grflags | (1 << 12)  ; application issued
    
    Debug "Win: " + Hex(win) + " grflags: " + Hex(grflags) + " x:" + Str(x) + " y:" + Str(y) + " w:" + Str(w) + " h:" + Str(h)
    
;     If wm_supports(*disp, "_NET_MOVERESIZE_WINDOW")
;       Debug "wm supports NET_MOVERESIZE_WINDOW"
;       Result = client_msg(*disp, win, "_NET_MOVERESIZE_WINDOW", grflags, x, y, w, h)
;     Else
      If (w < 1 Or h < 1) And (x >= 0 And y >= 0)
        Debug "XMoveWindow"
        Result = XMoveWindow(*disp, win, x, y)
      ElseIf (x < 0 Or y < 0) And (w >= 1 And h >= -1)
        Debug "XResizeWindow"
        Result = XResizeWindow(*disp, win, w, h)
      ElseIf x >= 0 And y >= 0 And w >= 1 And h >= 1
        Debug "XMoveResizeWindow"
        Result = XMoveResizeWindow(*disp, win, x, y, w, h)
      EndIf
;     EndIf
    
    ProcedureReturn Result
    
  EndProcedure
  
  
  
  
  Procedure.i set_parent(*disp, w, parent, x, y)
    
    XUnmapWindow(*disp, w)
    ;XMapWindow(*disp, parent)
    XSync(*disp, #False)
    
    XReparentWindow(*disp, w, parent, x, y)
    XMapWindow(*disp, w)
    Delay(2)
    XSync(*disp, #False)
    
  EndProcedure
  
  
  
  
  Procedure.i get_client_list(*disp, *size.Long)
    
    Protected *client_list
    
    
    *client_list = get_property(*disp, DefaultRootWindow(*disp), #XA_WINDOW, "_NET_CLIENT_LIST", *size)
    If *client_list = #Null
      *client_list = get_property(*disp, DefaultRootWindow(*disp), #XA_CARDINAL, "_WIN_CLIENT_LIST", *size)
    EndIf
    
    ;Debug "*clientlist: " + Str(*client_list) + " " + Str(*size\l) + " " + PeekS(*client_list, *size\l, #PB_UTF8)
    
    ProcedureReturn *client_list
    
  EndProcedure
  
  
  
  
  Procedure.i get_window_by_title(*disp, title$)
    
    Protected.i i, cnt, pid
    Protected *client_list.ArrayOfInteger
    Protected client_list_size.i
    Protected *client_machine, *desktop, *pid
    
    
    *client_list = get_client_list(*disp, @client_list_size)
    If *client_list
      cnt = client_list_size / SizeOf(Integer) - 1
      ;Debug "client_list_size: " + Str(cnt + 1)
      
      For i = 0 To cnt
        
        If *client_list\i[i]
          If get_window_title(*disp, *client_list\i[i]) = title$
            pid = *client_list\i[i]
            Break
          EndIf
        EndIf
        
      Next i
      
      FreeMemory(*client_list)
    EndIf
    
    ProcedureReturn pid
    
  EndProcedure
  
  
  
  
  Procedure.i list_windows(*disp, List WindowList.window_structure())
    
    Protected *client_list.ArrayOfInteger
    Protected client_list_size.i
    Protected *client_machine, *desktop, *pid
    Protected max_client_machine_len.i
    Protected Title$
    Protected i.i, cnt.i
    
    
    *client_list = get_client_list(*disp, @client_list_size)
    If *client_list
      cnt = client_list_size / SizeOf(Integer) - 1
      ;Debug "client_list_size: " + Str(cnt + 1)
      
      For i = 0 To cnt
        
        If *client_list\i[i]
          
          AddElement(WindowList())
          WindowList()\desktop = -666
          WindowList()\title$ = get_window_title(*disp, *client_list\i[i])
          
          ; client_machine
          *client_machine = get_property(*disp, *client_list\i[i], #XA_STRING, "WM_CLIENT_MACHINE", #Null)
          If *client_machine
            WindowList()\client_machine$ = PeekS(*client_machine, -1, #PB_UTF8)
            FreeMemory(*client_machine)
          EndIf
          
          ; desktop ID
          *desktop = get_property(*disp, *client_list\i[i], #XA_CARDINAL, "_NET_WM_DESKTOP", #Null)
          If *desktop = #Null
            *desktop = get_property(*disp, *client_list\i[i], #XA_CARDINAL, "_WIN_WORKSPACE", #Null)
          EndIf
          If *desktop
            WindowList()\desktop = PeekI(*desktop)
            FreeMemory(*desktop)
          EndIf
          
          ; pid
          *pid = get_property(*disp, *client_list\i[i], #XA_CARDINAL, "_NET_WM_PID", #Null)
          If *pid
            WindowList()\pid = *client_list\i[i]
            FreeMemory(*pid)
          EndIf
          
        EndIf
        
      Next i
      
      FreeMemory(*client_list)
    EndIf
    
    ProcedureReturn ListSize(WindowList())
    
  EndProcedure
  
  
  
  
  Procedure.i list_child_windows(*disp, win)
    
    Protected.i nchildren_return, root_return, parent_return, i
    Protected *children_return.ArrayOfInteger
    
    
    If XQueryTree(*disp, win, @root_return, @parent_return, @*children_return, @nchildren_return)
      Debug "root_return: " + Hex(root_return)
      Debug "parent_return: " + Hex(parent_return)
      Debug "nchildren_return: " + Str(nchildren_return)
      For i = 0 To nchildren_return - 1
        Debug Hex(*children_return\i[i]) + " " + get_window_title(*disp, *children_return\i[i])
      Next i
      If *children_return
        XFree(*children_return)
      EndIf
    EndIf
    
  EndProcedure
  
  
  
  
  Procedure.i open_display(Display$="")
    
    Protected *disp
    
    If Display$ = ""
      *disp = XOpenDisplay(#Null$)
    Else
      *disp = XOpenDisplay(Display$)
    EndIf
    
    ProcedureReturn *disp
    
  EndProcedure
  
  
  Procedure.i close_display(*disp)
    ProcedureReturn XCloseDisplay(*disp)
  EndProcedure
  
  
EndModule


CompilerIf #PB_Compiler_IsMainFile
  
  ;-Demo
  Define *disp, ActiveWindow.i, max_client_machine_len.i, client_machine_len
  Define NewList WindowList.wm::window_structure()
  
  
  If OpenConsole()
    
    *disp = wm::open_display(#Null$)
    If *disp
      ActiveWindow = wm::get_active_window(*disp)
      Debug "Active window: " + wm::get_window_title(*disp, ActiveWindow)
      
      ;wm::close_window(*disp, ActiveWindow)  ; don't do this :)
      
      ;wm::window_move_resize(*disp, ActiveWindow, 100, 100, 800, 600)
      
      If wm::list_windows(*disp, WindowList())
        
        ; find the longest client_machine name
        ForEach WindowList()
          client_machine_len = Len(WindowList()\client_machine$)
          If client_machine_len > max_client_machine_len
            max_client_machine_len = client_machine_len
          EndIf
        Next
        
        ForEach WindowList()
          If WindowList()\desktop <> -666
            Debug "0x" + RSet(Hex(WindowList()\pid), 8, "0") + " " + RSet(Str(WindowList()\desktop), 2) + " " + LSet(WindowList()\client_machine$, max_client_machine_len) + " " + WindowList()\title$
            PrintN("0x" + RSet(Hex(WindowList()\pid), 8, "0") + " " + RSet(Str(WindowList()\desktop), 2) + " " + LSet(WindowList()\client_machine$, max_client_machine_len) + " " + WindowList()\title$)
          Else
            Debug "0x" + RSet(Hex(WindowList()\pid), 8, "0") + " [x] " + LSet(WindowList()\client_machine$, max_client_machine_len) + " " + WindowList()\title$
            PrintN("0x" + RSet(Hex(WindowList()\pid), 8, "0") + " [x] " + LSet(WindowList()\client_machine$, max_client_machine_len) + " " + WindowList()\title$)
          EndIf
          
        Next
      EndIf
      
      wm::close_display(*disp)
    EndIf
    
    CompilerIf #PB_Compiler_Debugger
      Delay(5000)
    CompilerEndIf
    
    CloseConsole()
  EndIf
  
CompilerEndIf
infratec
Always Here
Always Here
Posts: 7618
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Linux WindowManager module

Post by infratec »

Added:

Code: Select all

Procedure.i activate_window(*disp, win)
Procedure.i get_window_by_title(*disp, title$)
Was needed for a bugfix :wink:
Post Reply