Home > Mobile >  Almost working way to grab a borderless window in Kivy
Almost working way to grab a borderless window in Kivy

Time:07-29

I am looking to replace the windows title bar for a borderless app, I found some solutions on the internet that didn't quite work for me so I tried to do it myself.

Although the grabbing the screen and moving part works, once you release the click, the window continues to follow the cursor until eventually the program stops responding and the task is terminated.

This is an example of code that I prepared with some indications on how it works:

from kivy.app import App
from win32api import GetSystemMetrics
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget
import pyautogui
import win32api
import re


Window.size=(600,300)
Window.borderless=True
#The following causes the window to open in the middle of the screen:
Window.top=((GetSystemMetrics(1)/2)-150)
Window.left=((GetSystemMetrics(0)/2)-300) 
#####################################################################

Builder.load_string("""
<Grab>
    GridLayout:
        size:root.width,root.height
        cols:2
        Label:
            id:label
            text:'A label'
        Button:
            id:button
            text:'The button that changes the window position'
            on_press: root.grab_window()
""")

class Grab(Widget):
    def grab_window(self):
        #The following saves the mouse position relative to the window:
        Static_Mouse_pos=re.findall('\d ',str(pyautogui.position()))
        Mouse_y=int(Static_Mouse_pos[1])-Window.top
        Mouse_x=int(Static_Mouse_pos[0])-Window.left
        ###############################################################
        #The following is what causes the window to follow the mouse position:
        while win32api.GetKeyState(0x01)<0: #In theory this should cause the loop to start as soon as it is clicked, I ruled out that it would start and end when the button was pressed and stopped being pressed because as soon as the screen starts to move, it stops being pressed.
            Relative_Mouse_pos=re.findall('\d ',str(pyautogui.position()))
            Window.left=(int(Relative_Mouse_pos[0])-Mouse_x)
            Window.top=(int(Relative_Mouse_pos[1])-Mouse_y)
            print(f'Mouse position: ({Mouse_x},{Mouse_y})') #To let you know the mouse position (Not necessary)
            print(f'Window position: ({Window.top},{Window.left})') #To let you know the position of the window (Not necessary)
            if win32api.GetKeyState(0x01)==0: #This is supposed to stop everything (Detects when you stop holding the click)
                break
        ######################################################################
class app(App):
    def build(self):
        return Grab()
if __name__=='__main__':
    app().run()

Is there a way to make it work fine? Or another way to grab a borderless window that might be effective?

I'm new to programming, so I apologize in advance for any nonsense you may read in my code.

EDIT: For some reason win32api.GetKeyState(0x01) is not updated once the click is done and the loop is started, nor does it help to make a variable take its value.

CodePudding user response:

I've finally come up with a solution but this may not be the best one.

( Some places where I made changes are marked with comment [Modified] )

from kivy.app import App
from win32api import GetSystemMetrics  # for getting screen size
from kivy.lang.builder import Builder
from kivy.core.window import Window
from kivy.uix.widget import Widget
import pyautogui
# import win32api
# import re

# set window size
# Window.size=(600,300)

# make the window borderless
Window.borderless = True

# The following causes the window to open in the middle of the screen :
Window.left = ((GetSystemMetrics(0) / 2) - Window.size[0] / 2)  # [Modified] for better flexibility
Window.top = ((GetSystemMetrics(1) / 2) - Window.size[1] / 2)  # [Modified] for better flexibility
#####################################################################

Builder.load_string("""
<Grab>
    GridLayout:
        size: root.width, root.height
        rows: 2  # [modified]
        
        Button:
            id: button
            text: "The button that changes the window position"
            size_hint_y: 0.2
            
        Label:
            id: label
            text: "A label"
""")


class Grab(Widget):
    
    # I'm sorry I just abandoned this lol
    """
    def grab_window(self):
        #The following saves the mouse position relative to the window:
        Static_Mouse_pos=re.findall('\d ',str(pyautogui.position()))
        Mouse_y=int(Static_Mouse_pos[1])-Window.top
        Mouse_x=int(Static_Mouse_pos[0])-Window.left
        ###############################################################
        #The following is what causes the window to follow the mouse position:
        while win32api.GetKeyState(0x01)<0: #In theory this should cause the loop to start as soon as it is clicked, I ruled out that it would start and end when the button was pressed and stopped being pressed because as soon as the screen starts to move, it stops being pressed.
            Relative_Mouse_pos=re.findall('\d ',str(pyautogui.position()))
            Window.left=(int(Relative_Mouse_pos[0])-Mouse_x)
            Window.top=(int(Relative_Mouse_pos[1])-Mouse_y)
            print(f'Mouse position: ({Mouse_x},{Mouse_y})') #To let you know the mouse position (Not necessary)
            print(f'Window position: ({Window.top},{Window.left})') #To let you know the position of the window (Not necessary)
            if win32api.GetKeyState(0x01)==0: #This is supposed to stop everything (Detects when you stop holding the click)
                break
        ######################################################################
    """
    
    def on_touch_move(self, touch):
        
        if self.ids.button.state == "down":  # down | normal
            # button is pressed
    
            # mouse pos relative to screen , list of int
            # top left (0, 0) ; bottom right (max,X, maxY)
            mouse_pos = [pyautogui.position()[0], pyautogui.position()[1]]
        
            # mouse pos relative to the window
            # ( normal rectangular coordinate sys. )
            mouse_x = touch.pos[0]
            mouse_y = Window.size[1] - touch.pos[1]  # since the coordinate sys. are different , just to converse it into the same
            
            # give up using touch.dx and touch.dy , too lag lol
            
            Window.left = mouse_pos[0] - mouse_x
            Window.top = mouse_pos[1] - mouse_y
            

class MyApp(App):  # [Modified] good practice using capital letter for class name
    
    def build(self):
        return Grab()


if __name__ == "__main__":
    MyApp().run()

I just gave up using button on_press or on_touch_down as suggested in the comment since it requires manual update for the mouse position.

Instead , I try using the Kivy built-in function ( ? ) on_touch_move. It is fired when a mouse motion is detected inside the windows by the application itself. ( much more convenient compared with manual checking lol )

The concepts of window positioning are similar to yours , which is mouse pos relative to screen - mouse pos relative to app window. But the coordinate system used by Pyautogui and Kivy 's window are different , therefore I did some conversion for this as seen in the code above.

But I'm not sure whether the unit used by Pyautogui and Kivy for mouse positioning is the same or not ( ? ), so it would not be as smooth as expected / ideal case when drag-and-dropping the window via the button. Also the time delay for updating when on_touch_move of the kivy app. That's the reason why I think it may be no the best answer for your question.

Any other solutions / suggestions / improvements etc. are welcome : )


Simplified Code For Copy-Paste :

#
# Windows Application
# Borderless window with button press to move window
#

import kivy
from kivy.app import App
from kivy.core.window import Window
from kivy.uix.widget import Widget
from kivy.clock import Clock

from win32api import GetSystemMetrics  # for getting screen size
import pyautogui  # for getting mouse pos

# set window size
# Window.size=(600,300)

# make the window borderless
Window.borderless = True

# set init window pos : center
# screen size / 2 - app window size / 2
Window.left = (GetSystemMetrics(0) / 2) - (Window.size[0] / 2)
Window.top = (GetSystemMetrics(1) / 2) - (Window.size[1] / 2)


kivy.lang.builder.Builder.load_string("""
<GrabScreen>
    Button:
        id: move_window_button
        text: "[b]. . .[/b]"
        font_size: 25
        markup: True
        width: root.width / 2
        height: self.texture_size[1] * 1.5
        border: 25, 25, 25, 25
        
    Label:
        id: this_label
        text: "Hello World !"
        font_size: 25
        size: self.texture_size
""")


class GrabScreen(Widget):
    
    def update(self, dt):
        # button for moving window
        self.ids.move_window_button.center_x = self.center_x
        self.ids.move_window_button.top = self.top
        
        # label
        self.ids.this_label.center = self.center
        
        
    def on_touch_move(self, touch):
        # when touching app screen and moving
        
        if self.ids.move_window_button.state == "down":  # down | normal
            # (button move_window_button is pressed) and (mouse is moving)
    
            # mouse pos relative to screen , list of int
            # top left (0, 0) ; bottom right (max,X, maxY)
            mouse_pos = [pyautogui.position()[0], pyautogui.position()[1]]
        
            # mouse pos relative to the window
            # ( normal rectangular coordinate sys. )
            mouse_x = touch.pos[0]
            # since the coordinate sys. are different , just to converse it to fit that of pyautogui
            mouse_y = Window.size[1] - touch.pos[1]
            
            # update app window pos
            Window.left = mouse_pos[0] - mouse_x
            Window.top = mouse_pos[1] - mouse_y
            

class MyApp(App):
    grabScreen = GrabScreen()
    
    def build(self):
        # schedule update
        Clock.schedule_interval(self.grabScreen.update, 0.1)
        
        return self.grabScreen


if __name__ == "__main__":
    MyApp().run()

Reference

Kivy Motion Event

  • Related