summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien <contact@julienpro.com>2023-07-12 14:35:06 +0200
committerJulien <contact@julienpro.com>2023-07-12 14:35:06 +0200
commit61e83b072b92b3015b4ec53775130a72c3fab4f4 (patch)
tree87e53b90153ed901ad9644eaa5499094023fe262
parent86145dea370ff85a7b78e8f633235101f3365ec8 (diff)
Implement x-move-win-name-screen
-rw-r--r--CHANGELOG.md16
-rw-r--r--README.md36
-rw-r--r--command_callbacks.py59
-rw-r--r--db.py12
-rwxr-xr-xlidecli.py14
5 files changed, 133 insertions, 4 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md
index a76e072..c402fad 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,9 +1,23 @@
# Changelog
All changes to the project will be documented in this file.
+## 1.0.2 - 2023.07.12
+
+### Commands added
+
+- Added `x-move-win-name-screen` to move Windows on a specific screen
+
+### Other
+
+- Implement command callbacks in addition to parsing callbacks
+
## 1.0.2 - 2023.07.11
-- Added command x-focus-name-nth to focus on the nth instance of the underlying window name
+### Commands added
+
+- Added `command x-focus-name-nth` to focus on the nth instance of the underlying window name
+
+### Other
- Added the -x option to wmctrl
- Script can be executed without setting the working direcotry
- Added this changelog
diff --git a/README.md b/README.md
index f78a68c..123178d 100644
--- a/README.md
+++ b/README.md
@@ -40,6 +40,17 @@ The code is a Python 3 script. You just need to clone this repository. Some exam
./lidecli.py x-win-name-maximize Firefox
```
+**Focus on the nth Window of a specific program**
+```
+./lidecli.py x-focus-name-nth kitty 1
+./lidecli.py x-focus-name-nth kitty 2
+```
+**Move a window to screen 3 (eg the third monitor)**
+```
+./lidecli.py x-move-win-name-screen terminal 3
+```
+
+
## Command currently implemented
As I am currently using KDE Plasma (very great except its command-line API) on X, the current database contains around 170 ready-to-use commands, mainly using **KDE API with qdbus**, and **wmctrl**, **xprop** or **xwininfo**.
@@ -151,7 +162,7 @@ If you launch `./lidecli.py x-get-winid Firefox`, then the command executed will
If you just to put the argument(s) at the end of the command line, it's not mandatory to add `#1#`. By default, all needed arguments mentionned will be forwarded to the end of the command launched.
-### Custom Python callbacks
+### Custom Python parsing callbacks
Lidecli supports custom Python callbacks, mainly to parse the output of a command.
@@ -166,7 +177,7 @@ Let's take the example below:
"versions_working": [("x11", "all")],
"versions_not_working": [],
"command": "xprop -id",
- "callback": {
+ "callback_parser": {
"function": "xprop_parser",
"output_key": "win_maximized"
},
@@ -180,6 +191,27 @@ A callback should return a dictionary of values. The result returned to the end-
In this case, we just need to write one parser function for xprop, and then returns the relevant key in our various commands.
+### Custom Pythong command callbacks
+
+Instead of calling a command line tool, you can call a Python function:
+
+```
+{
+ "name": "x-move-win-name-screen",
+ "description": "Move a window specified by its name to a specific monitor",
+ "forwarded_arguments": [
+ { "name": "WinName", "description": "The substring to match the Window name"},
+ { "name": "Monitor", "description": "The index of the monitor"}
+ ],
+ "callback_command": "move_win_to_screen",
+ "versions_working": [("x11", "all")],
+ "versions_not_working": [],
+ "tags": ["x11", "windows", "wmctrl", "xrandr" ]
+},
+```
+
+Here, the function `move_win_to_screen` in the file `command_callbacks.py` will be executed.
+
### Saving temporarily the results of commands
It may be useful to save temporarily the result of a command. It's particularly useful to save the current focused Window to be restored later, so when you launch the command through a keybinding, your focused window is not lost.
diff --git a/command_callbacks.py b/command_callbacks.py
new file mode 100644
index 0000000..86b25a6
--- /dev/null
+++ b/command_callbacks.py
@@ -0,0 +1,59 @@
+
+import subprocess
+import re
+
+def exec_command(command):
+ try:
+ ret = subprocess.check_output(command, shell=True, stderr=subprocess.STDOUT)
+ except subprocess.CalledProcessError as e:
+ print("Command '{}' return with error (code {}): {}".format(e.cmd, e.returncode, e.output))
+ return False
+ ret = ret.decode('utf-8')
+ return ret
+
+def get_windows_coordinates(win_name):
+ command = 'wmctrl -G -l -x |grep {}'.format(win_name)
+ ret = exec_command(command)
+ lines = ret.split("\n")
+ windows = []
+ for line in lines:
+ if line.strip() == '':
+ continue
+ sections = line.split()
+ win = {"id": sections[0], "x": int(sections[2]), "y": int(sections[3]), "width": int(sections[4]), "height": int(sections[5])}
+ windows.append(win)
+ return windows
+
+def get_screens():
+ ret = exec_command('xrandr | grep " connected"')
+ lines = ret.split("\n")
+ screens = []
+ for line in lines:
+ if line.strip() == '':
+ continue
+ # Example: eDP-1 connected 1920x1200+3840+0 (normal left inverted right x axis y axis) 288mm x 180mm
+ m = re.match(r'(.+?) connected (?:primary )?(\d+)x(\d+)\+(\d+)\+(\d+)', line)
+ screen = {"name": m.group(1), "h": int(m.group(2)), 'v': int(m.group(3)), 'offset_h': int(m.group(4)), 'offset_v': int(m.group(5))}
+ screens.append(screen)
+ ordered_screens = sorted(screens, key=lambda d: d['offset_h'])
+ return ordered_screens
+
+def move_win(win_id, pos_x, pos_y, width, height):
+ command = "wmctrl -i -r {} -e 0,{},{},{},{}".format(win_id, pos_x, pos_y, width, height)
+ ret = exec_command(command)
+ return ret
+
+def move_win_to_screen(args):
+ screens = get_screens()
+ windows = get_windows_coordinates(args.WinName)
+ if len(screens) < int(args.Monitor):
+ return False
+ for win in windows:
+ for screen in screens:
+ if win['x'] > screen['offset_h'] and win['x'] < screen['offset_h'] + screen['h']:
+ diff_h = win['x'] - screen['offset_h']
+ print(diff_h)
+ new_pos_x = screens[int(args.Monitor)-1]['offset_h']+diff_h
+ move_win(win['id'], new_pos_x, win['y'], win['width'], win['height'])
+
+ return "toto" \ No newline at end of file
diff --git a/db.py b/db.py
index b42362d..c374cf8 100644
--- a/db.py
+++ b/db.py
@@ -303,6 +303,18 @@ def commands():
"tags": ["x11", "windows", "wmctrl" ]
},
{
+ "name": "x-move-win-name-screen",
+ "description": "Move a window specified by its name to a specific monitor",
+ "forwarded_arguments": [
+ { "name": "WinName", "description": "The substring to match the Window name"},
+ { "name": "Monitor", "description": "The index of the monitor"}
+ ],
+ "callback_command": "move_win_to_screen",
+ "versions_working": [("x11", "all")],
+ "versions_not_working": [],
+ "tags": ["x11", "windows", "wmctrl", "xrandr" ]
+ },
+ {
"name": "kde-toggle-win-fullscreen",
"description": "Toogle fullscreen for the current Window",
"command": "qdbus org.kde.kglobalaccel /component/kwin org.kde.kglobalaccel.Component.invokeShortcut 'Window Fullscreen'",
diff --git a/lidecli.py b/lidecli.py
index aa717ec..645b6bc 100755
--- a/lidecli.py
+++ b/lidecli.py
@@ -7,6 +7,7 @@ import json
import os
import configparser
import parsing_callbacks
+import command_callbacks
import db
version = "1.0.2"
@@ -76,11 +77,21 @@ def exec_command(args):
print(read_temporary_variable(int(args.index)))
return
command = get_command_by_name(args.command)
+ str_command = None
if "command" in command:
str_command = command["command"]
- else:
+ elif "commands" in command:
str_command = ' && '.join(command['commands'])
+
+ # Callback command
+ if str_command == None:
+ function = getattr(command_callbacks, command["callback_command"])
+ ret = function(args)
+ # ret = ret.decode('utf-8')
+ print(ret, end='')
+ return
+
for option in command.get('forwarded_options', []):
param = option['param'].replace('-', '')
if getattr(args, param) == True:
@@ -116,6 +127,7 @@ def exec_command(args):
function = getattr(parsing_callbacks, command["callback_parser"]["function"])
ret = function(ret)
ret = ret[command['callback_parser']['output_key']]
+ print(ret, end='')
# print(ret["win_maximized"], end='')
else:
print(ret, end='')