Compare commits
22 Commits
131351c258
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
4b8599132f
|
|||
|
1cae22ad2c
|
|||
|
a859bab33d
|
|||
|
220f8cd810
|
|||
|
a0bec00988
|
|||
| 7fb1ea8cb3 | |||
| 0d2f52347d | |||
| 3eb15736f3 | |||
| 7460d38653 | |||
| 248e5a2c05 | |||
| 10e0de5b05 | |||
| 2930d71889 | |||
|
49ba7ddb41
|
|||
| d1e13093c5 | |||
| e22fbbd504 | |||
| 2c253da55d | |||
| de595a0802 | |||
| a21e716475 | |||
| 11806119df | |||
| f4af09eb34 | |||
| da0af70b1e | |||
| c6eb56ef29 |
@@ -1,3 +1,5 @@
|
||||
*.so
|
||||
*.vim
|
||||
*p.yml
|
||||
.num.json
|
||||
result
|
||||
|
||||
@@ -17,10 +17,10 @@ KUTU means box in turkish, which is my idea behind implementing this WM.
|
||||
|
||||
## Installation & Usage
|
||||
|
||||
You need to run `./compile.sh` to built the c library.
|
||||
You need to run `./compile.sh` to build the c library.
|
||||
<br>
|
||||
Then add `kutu.rb` to your `~/.xinitrc` file.
|
||||
|
||||
## TODO
|
||||
|
||||
Add `ruby` like configuration files support.
|
||||
Add better `ruby` configuration files support.
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
require "socket"
|
||||
|
||||
SOCK_PATH = "/tmp/kutu.sock"
|
||||
|
||||
cli_addr_path = "/tmp/kutu_client#{Process.pid}.sock"
|
||||
File.delete cli_addr_path if File.exist? cli_addr_path
|
||||
|
||||
client = Socket.new :UNIX, :DGRAM
|
||||
client.bind Socket.pack_sockaddr_un(cli_addr_path)
|
||||
|
||||
client.send ARGV.join(" "), 0, Socket.pack_sockaddr_un(SOCK_PATH)
|
||||
|
||||
data, = client.recvfrom 1024
|
||||
|
||||
puts data
|
||||
Executable
+91
@@ -0,0 +1,91 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# Require X-kutu library for all x11 functions
|
||||
|
||||
require_relative "../src/ruby/X-kutu"
|
||||
|
||||
|
||||
# Require dependencies
|
||||
|
||||
require 'json'
|
||||
require 'socket'
|
||||
|
||||
|
||||
# Initialize X
|
||||
|
||||
if X.deploy >= 0
|
||||
puts "Started kutu WM for X11 successfully! :)"
|
||||
else
|
||||
raise "Failed to deploy X, try running using xinit or startx."
|
||||
end
|
||||
|
||||
|
||||
# Require submodules
|
||||
|
||||
require_relative "../src/ruby/utils"
|
||||
require_relative "../src/ruby/controller"
|
||||
require_relative "../src/ruby/window"
|
||||
require_relative "../src/ruby/workspace"
|
||||
require_relative "../src/ruby/events"
|
||||
require_relative "../src/ruby/commands"
|
||||
require_relative "../src/ruby/factory"
|
||||
require_relative "../src/ruby/config"
|
||||
|
||||
# Cleanup on exit
|
||||
|
||||
at_exit do
|
||||
X.cleanup
|
||||
puts "Exited kutu WM, sadly :("
|
||||
end
|
||||
|
||||
|
||||
# Globals
|
||||
|
||||
$monitors = {}
|
||||
$windows = {}
|
||||
|
||||
$all_windows = []
|
||||
|
||||
$keybind_actions = {}
|
||||
$mousebind_actions = {}
|
||||
|
||||
$mouse_data = {}
|
||||
|
||||
$root = X.get_root
|
||||
$rect = {}
|
||||
|
||||
|
||||
# Initialize monitors
|
||||
|
||||
load_monitors!
|
||||
|
||||
|
||||
# Run startup script
|
||||
|
||||
load_config!
|
||||
|
||||
# Setup unix socket
|
||||
|
||||
SOCK_PATH = "/tmp/kutu.sock"
|
||||
File.delete SOCK_PATH if File.exist? SOCK_PATH
|
||||
|
||||
$socket = Socket.new :UNIX, :DGRAM
|
||||
$socket.bind Socket.pack_sockaddr_un(SOCK_PATH)
|
||||
|
||||
|
||||
# Main loop
|
||||
|
||||
loop do
|
||||
sleep 0.001
|
||||
|
||||
if IO.select([$socket], nil, nil, 0)
|
||||
command, sender = $socket.recvfrom 1024
|
||||
reply = handle_command command
|
||||
$socket.send JSON.generate(reply), 0, sender
|
||||
end
|
||||
|
||||
event_pointer = X.next_event
|
||||
handle_event X.translate_event(event_pointer) if !event_pointer.null?
|
||||
|
||||
X.flush
|
||||
end
|
||||
Generated
+27
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"nodes": {
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1783148766,
|
||||
"narHash": "sha256-uslt2pqShTIXDdAHRHv2QkYLsVdY8Oqwz0EA48/RSM8=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a50de1b7d8a586adc18d2395c19de7d6058e6030",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-26.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
{
|
||||
description = "kutu wm environment";
|
||||
|
||||
inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
||||
|
||||
outputs = { self, nixpkgs }:
|
||||
let
|
||||
system = "x86_64-linux";
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
};
|
||||
in {
|
||||
devShells.${system}.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
(ruby.withPackages (ps: [
|
||||
ps.ffi
|
||||
]))
|
||||
gcc
|
||||
pkg-config
|
||||
|
||||
libxcb
|
||||
xcbutilwm
|
||||
xrandr
|
||||
xprop
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "kutu dev shell"
|
||||
'';
|
||||
};
|
||||
packages.${system}.default = pkgs.callPackage ./package.nix {};
|
||||
};
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
# Require X-kutu library for all x11 functions
|
||||
|
||||
require_relative "./src/X-kutu"
|
||||
|
||||
# Initialize X
|
||||
|
||||
if X.deploy >= 0
|
||||
puts "Started kutu WM for X11 successfully!"
|
||||
else
|
||||
raise "Failed to deploy X"
|
||||
end
|
||||
|
||||
|
||||
# Require modules
|
||||
|
||||
require_relative "./src/utils"
|
||||
require_relative "./src/node"
|
||||
require_relative "./src/workspace"
|
||||
require_relative "./src/events"
|
||||
|
||||
|
||||
# Cleanup on exit
|
||||
|
||||
at_exit do
|
||||
X.cleanup
|
||||
puts "Exited kutu WM, sadly :("
|
||||
end
|
||||
|
||||
|
||||
# Globals
|
||||
|
||||
$monitors = {}
|
||||
$workspaces = {}
|
||||
$windows = {}
|
||||
|
||||
$keybind_actions = {}
|
||||
$mousebind_actions = {}
|
||||
|
||||
$mouse_data = {}
|
||||
|
||||
$root = X.get_root
|
||||
$rect = {}
|
||||
|
||||
|
||||
# Initialize monitors
|
||||
|
||||
refresh_monitors!
|
||||
|
||||
|
||||
# Add keybinds
|
||||
|
||||
load "./src/bindings.rb"
|
||||
|
||||
|
||||
# Initialize workspaces
|
||||
|
||||
$workspaces[:main] = Workspace.new(:main)
|
||||
|
||||
|
||||
# Main loop
|
||||
|
||||
loop do
|
||||
event = X.wait_for_event
|
||||
handle_event event
|
||||
end
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
{ stdenv
|
||||
, ruby
|
||||
, makeWrapper
|
||||
, pkg-config
|
||||
, libxcb
|
||||
, xcbutilwm
|
||||
, xrandr
|
||||
, xprop
|
||||
, rubyPackages
|
||||
, gcc
|
||||
, lib
|
||||
}:
|
||||
|
||||
stdenv.mkDerivation {
|
||||
pname = "kutu";
|
||||
version = "0.1.0";
|
||||
|
||||
src = ./.;
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkg-config
|
||||
makeWrapper
|
||||
gcc
|
||||
];
|
||||
|
||||
buildInputs = [
|
||||
(ruby.withPackages (ps: [
|
||||
ps.ffi
|
||||
]))
|
||||
libxcb
|
||||
xcbutilwm
|
||||
xrandr
|
||||
xprop
|
||||
];
|
||||
|
||||
buildPhase = ''
|
||||
mkdir -p build
|
||||
|
||||
gcc -shared -fPIC \
|
||||
-Wall -Wextra \
|
||||
-o build/X-kutu.so \
|
||||
src/c/X-kutu.c \
|
||||
$(pkg-config --libs --cflags xcb xcb-icccm);
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
mkdir -p $out/bin
|
||||
mkdir -p $out/build
|
||||
mkdir -p $out/src
|
||||
|
||||
cp bin/kutu.rb $out/bin/kutu.rb
|
||||
cp bin/kutu-run.rb $out/bin/kutu-run.rb
|
||||
|
||||
cp build/X-kutu.so $out/build/
|
||||
cp -r src/. $out/src/
|
||||
|
||||
wrapProgram $out/bin/kutu.rb \
|
||||
--prefix PATH : ${lib.makeBinPath [
|
||||
xrandr
|
||||
xprop
|
||||
]}
|
||||
'';
|
||||
}
|
||||
+12
-6
@@ -1,4 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
DIR="$(cd -- "$(dirname -- "$0")" && pwd)"
|
||||
@@ -8,14 +9,10 @@ if ! XCB=$(pkg-config --cflags --libs xcb xcb-icccm 2>/dev/null); then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! xrandr --version >/dev/null 2>&1; then
|
||||
echo "Error: xrandr not found. Please install xrandr." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$DIR/build"
|
||||
|
||||
gcc -shared -fPIC -Wall -Wextra -o "$DIR/build/X-kutu.so" "$DIR/X-kutu.c" $XCB
|
||||
# shellcheck disable=SC2086
|
||||
gcc -shared -fPIC -Wall -Wextra -o "$DIR/build/X-kutu.so" "$DIR/src/c/X-kutu.c" $XCB
|
||||
|
||||
if [ ! -f "$DIR/build/X-kutu.so" ]; then
|
||||
echo "Error: compilation failed." >&2
|
||||
@@ -23,3 +20,12 @@ if [ ! -f "$DIR/build/X-kutu.so" ]; then
|
||||
else
|
||||
echo "Success: $DIR/build/X-kutu.so is compiled."
|
||||
fi
|
||||
|
||||
for f in "$DIR/bin/"* "$DIR/src/shell/"*; do
|
||||
chmod +x "$f" || {
|
||||
echo "Error: Failed to chmod $f" >&2
|
||||
exit 1
|
||||
}
|
||||
done
|
||||
|
||||
echo -e "\e[32mAll done! Build successful.\e[0m"
|
||||
@@ -1,18 +0,0 @@
|
||||
keybind(24) do |event|
|
||||
X.kill event[:window]
|
||||
end
|
||||
|
||||
keybind(25) do |_event|
|
||||
pid = spawn("kitty")
|
||||
Process.detach pid
|
||||
end
|
||||
|
||||
keybind(26) do |_event|
|
||||
$workspaces.each_value(&:close_all)
|
||||
exit 1
|
||||
end
|
||||
|
||||
|
||||
mousebind 1
|
||||
|
||||
mousebind 3
|
||||
+48
-82
@@ -1,23 +1,5 @@
|
||||
#define CLEANMASK(m) ((m & ~0x80))
|
||||
|
||||
// Definitions for modifier keys
|
||||
#define SUPER XCB_MOD_MASK_4
|
||||
#define ALT XCB_MOD_MASK_1
|
||||
#define CTRL XCB_MOD_MASK_CONTROL
|
||||
#define SHIFT XCB_MOD_MASK_SHIFT
|
||||
|
||||
// Change this to change the modifier key you want to use
|
||||
#define MOD SUPER
|
||||
|
||||
// Standard headers
|
||||
#include <err.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// XCB header
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
// Self-header
|
||||
#include "X-kutu.h"
|
||||
|
||||
// Global variables
|
||||
// Connection to X server
|
||||
@@ -27,27 +9,8 @@ xcb_screen_t *scr;
|
||||
// Currently focused window
|
||||
xcb_window_t focuswin;
|
||||
|
||||
// Geometry structure for rectangles and positions
|
||||
typedef struct Geometry {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} Geometry;
|
||||
|
||||
// Event structure to represent various X events
|
||||
typedef struct Event {
|
||||
int32_t type;
|
||||
uint32_t window;
|
||||
int8_t override_redirect;
|
||||
uint32_t btn;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t state;
|
||||
int8_t is_root;
|
||||
} Event;
|
||||
// Flush
|
||||
void flush(void) { xcb_flush(conn); }
|
||||
|
||||
// Cleanup function to close the X connection on exit
|
||||
void cleanup(void) {
|
||||
@@ -57,18 +20,18 @@ void cleanup(void) {
|
||||
|
||||
// Keybind function to setup a key grab if the keycode is clicked along with the
|
||||
// MOD key
|
||||
void add_keybind(int key) {
|
||||
xcb_grab_key(conn, 0, scr->root, MOD, key, XCB_GRAB_MODE_ASYNC,
|
||||
void add_keybind(int key, int mod) {
|
||||
xcb_grab_key(conn, 0, scr->root, mod, key, XCB_GRAB_MODE_ASYNC,
|
||||
XCB_GRAB_MODE_ASYNC);
|
||||
}
|
||||
|
||||
// Mousebind function to setup a mouse button grab if the button is clicked
|
||||
// along with the MOD key
|
||||
void add_mousebind(int button) {
|
||||
void add_mousebind(int button, int mod) {
|
||||
xcb_grab_button(conn, 0, scr->root,
|
||||
XCB_EVENT_MASK_BUTTON_PRESS | XCB_EVENT_MASK_BUTTON_RELEASE,
|
||||
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, scr->root, XCB_NONE,
|
||||
button, MOD);
|
||||
button, mod);
|
||||
}
|
||||
|
||||
// Deploy function to initialize the X connection, set up event masks, and
|
||||
@@ -122,33 +85,51 @@ void subscribe(xcb_window_t win) {
|
||||
|
||||
// Kill a window
|
||||
void kill(xcb_window_t window) {
|
||||
xcb_kill_client(conn, window);
|
||||
xcb_intern_atom_cookie_t wm_protocols_cookie =
|
||||
xcb_intern_atom(conn, 1, 12, "WM_PROTOCOLS");
|
||||
xcb_intern_atom_cookie_t wm_delete_window_cookie =
|
||||
xcb_intern_atom(conn, 0, 16, "WM_DELETE_WINDOW");
|
||||
|
||||
xcb_intern_atom_reply_t *wm_protocols =
|
||||
xcb_intern_atom_reply(conn, wm_protocols_cookie, NULL);
|
||||
xcb_intern_atom_reply_t *wm_delete_window =
|
||||
xcb_intern_atom_reply(conn, wm_delete_window_cookie, NULL);
|
||||
|
||||
if (!wm_protocols || !wm_delete_window) {
|
||||
// fallback to force kill
|
||||
xcb_kill_client(conn, window);
|
||||
xcb_flush(conn);
|
||||
return;
|
||||
}
|
||||
|
||||
xcb_client_message_event_t ev = {0};
|
||||
ev.response_type = XCB_CLIENT_MESSAGE;
|
||||
ev.window = window;
|
||||
ev.type = wm_protocols->atom;
|
||||
ev.format = 32;
|
||||
ev.data.data32[0] = wm_delete_window->atom;
|
||||
ev.data.data32[1] = XCB_CURRENT_TIME;
|
||||
|
||||
xcb_send_event(conn, 0, window, XCB_EVENT_MASK_NO_EVENT, (char *)&ev);
|
||||
xcb_flush(conn);
|
||||
|
||||
free(wm_protocols);
|
||||
free(wm_delete_window);
|
||||
}
|
||||
|
||||
// Destroy a window
|
||||
void destroy(xcb_window_t win) {
|
||||
xcb_destroy_window(conn, win);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
void destroy(xcb_window_t win) { xcb_destroy_window(conn, win); }
|
||||
|
||||
// Show a window
|
||||
void show(xcb_window_t window) {
|
||||
xcb_map_window(conn, window);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
void show(xcb_window_t window) { xcb_map_window(conn, window); }
|
||||
|
||||
// Hide a window
|
||||
void hide(xcb_window_t window) {
|
||||
xcb_unmap_window(conn, window);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
void hide(xcb_window_t window) { xcb_unmap_window(conn, window); }
|
||||
|
||||
// Bring a window to the top of the stack
|
||||
void send_to_top(xcb_window_t win) {
|
||||
uint32_t values[1] = {XCB_STACK_MODE_ABOVE};
|
||||
xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_STACK_MODE, values);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
// Get the geometry of a window
|
||||
@@ -161,12 +142,12 @@ Geometry get_geometry(xcb_window_t win) {
|
||||
}
|
||||
|
||||
// Get the current pointer position
|
||||
Geometry get_pointer(void) {
|
||||
PointerInfo get_pointer(void) {
|
||||
xcb_query_pointer_reply_t *geom;
|
||||
geom = xcb_query_pointer_reply(conn, xcb_query_pointer(conn, scr->root), 0);
|
||||
Geometry g = {geom->root_x, geom->root_y, 0, 0};
|
||||
PointerInfo p = {geom->root_x, geom->root_y, geom->child};
|
||||
free(geom);
|
||||
return g;
|
||||
return p;
|
||||
}
|
||||
|
||||
// Get the screen geometry
|
||||
@@ -179,7 +160,6 @@ Geometry get_screen(void) {
|
||||
// the center)
|
||||
void warp_pointer(xcb_window_t win, int x, int y) {
|
||||
xcb_warp_pointer(conn, XCB_NONE, win, 0, 0, 0, 0, x, y);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
// Move a window to a specific position
|
||||
@@ -187,7 +167,6 @@ void move_window(xcb_window_t win, int x, int y) {
|
||||
uint32_t values[2] = {x, y};
|
||||
xcb_configure_window(conn, win, XCB_CONFIG_WINDOW_X | XCB_CONFIG_WINDOW_Y,
|
||||
values);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
// Resize a window to specific dimensions
|
||||
@@ -195,7 +174,6 @@ void resize_window(xcb_window_t win, int width, int height) {
|
||||
uint32_t values[2] = {width, height};
|
||||
xcb_configure_window(
|
||||
conn, win, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
xcb_size_hints_t get_wm_n_hints(xcb_window_t win) {
|
||||
@@ -240,8 +218,6 @@ void set_wm_state(xcb_window_t win, int state) {
|
||||
|
||||
xcb_change_property(conn, XCB_PROP_MODE_REPLACE, win, WM_STATE, WM_STATE, 32,
|
||||
2, data);
|
||||
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
xcb_window_t draw_rectangle(int x, int y, int width, int height,
|
||||
@@ -282,9 +258,6 @@ xcb_window_t draw_rectangle(int x, int y, int width, int height,
|
||||
xcb_rectangle_t rect = {0, 0, width, height}; // relative to window
|
||||
xcb_poly_fill_rectangle(conn, win, gc, 1, &rect);
|
||||
|
||||
// Flush the connection to send commands to the server
|
||||
xcb_flush(conn);
|
||||
|
||||
return win;
|
||||
}
|
||||
|
||||
@@ -297,23 +270,18 @@ void grab_pointer(xcb_window_t win) {
|
||||
XCB_EVENT_MASK_POINTER_MOTION_HINT,
|
||||
XCB_GRAB_MODE_ASYNC, XCB_GRAB_MODE_ASYNC, XCB_NONE, XCB_NONE,
|
||||
XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
|
||||
void ungrab_pointer(void) {
|
||||
xcb_ungrab_pointer(conn, XCB_CURRENT_TIME);
|
||||
xcb_flush(conn);
|
||||
}
|
||||
void ungrab_pointer(void) { xcb_ungrab_pointer(conn, XCB_CURRENT_TIME); }
|
||||
|
||||
xcb_generic_event_t *next_event(void) { return xcb_poll_for_event(conn); }
|
||||
|
||||
// Wait for an event and return it as an Event structure
|
||||
// This function is blocking
|
||||
// The event is sent by value, so no need to free anything
|
||||
Event wait_for_event(void) {
|
||||
Event translate_event(xcb_generic_event_t *ev) {
|
||||
Event ret = {0};
|
||||
|
||||
xcb_generic_event_t *ev;
|
||||
ev = xcb_wait_for_event(conn);
|
||||
|
||||
if (!ev)
|
||||
errx(1, "xcb connection broken");
|
||||
|
||||
@@ -358,14 +326,13 @@ Event wait_for_event(void) {
|
||||
|
||||
case XCB_BUTTON_PRESS: {
|
||||
xcb_button_press_event_t *e = (xcb_button_press_event_t *)ev;
|
||||
if (!e->child)
|
||||
break;
|
||||
ret.type = 6;
|
||||
ret.window = e->child;
|
||||
ret.is_root = e->child == scr->root;
|
||||
ret.x = e->event_x;
|
||||
ret.y = e->event_y;
|
||||
ret.btn = e->detail;
|
||||
ret.state = e->state;
|
||||
} break;
|
||||
|
||||
case XCB_MOTION_NOTIFY: {
|
||||
@@ -428,7 +395,6 @@ Event wait_for_event(void) {
|
||||
} break;
|
||||
}
|
||||
|
||||
xcb_flush(conn);
|
||||
free(ev);
|
||||
return ret;
|
||||
}
|
||||
+107
@@ -0,0 +1,107 @@
|
||||
#ifndef X_KUTU_H
|
||||
#define X_KUTU_H
|
||||
|
||||
// Standard headers
|
||||
#include <err.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
|
||||
// XCB headers
|
||||
#include <xcb/xcb.h>
|
||||
#include <xcb/xcb_icccm.h>
|
||||
|
||||
// Macro to clean modifier masks
|
||||
#define CLEANMASK(m) ((m & ~0x80))
|
||||
|
||||
// Definitions for modifier keys
|
||||
#define SUPER XCB_MOD_MASK_4
|
||||
#define ALT XCB_MOD_MASK_1
|
||||
#define CTRL XCB_MOD_MASK_CONTROL
|
||||
#define SHIFT XCB_MOD_MASK_SHIFT
|
||||
|
||||
// Change this to change the modifier key you want to use
|
||||
#define MOD SUPER
|
||||
|
||||
// Forward declarations of global variables
|
||||
extern xcb_connection_t *conn;
|
||||
extern xcb_screen_t *scr;
|
||||
extern xcb_window_t focuswin;
|
||||
|
||||
// Geometry structure for rectangles and positions
|
||||
typedef struct Geometry {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint16_t width;
|
||||
uint16_t height;
|
||||
} Geometry;
|
||||
|
||||
typedef struct PointerInfo {
|
||||
int16_t x;
|
||||
int16_t y;
|
||||
uint32_t window;
|
||||
} PointerInfo;
|
||||
|
||||
// Event structure to represent various X events
|
||||
typedef struct Event {
|
||||
int32_t type;
|
||||
uint32_t window;
|
||||
int8_t override_redirect;
|
||||
uint32_t btn;
|
||||
int32_t x;
|
||||
int32_t y;
|
||||
uint32_t height;
|
||||
uint32_t width;
|
||||
uint32_t state;
|
||||
int8_t is_root;
|
||||
} Event;
|
||||
|
||||
// Function prototypes
|
||||
void flush(void);
|
||||
int deploy(void);
|
||||
void cleanup(void);
|
||||
|
||||
void add_keybind(int key, int mod);
|
||||
void add_mousebind(int button, int mod);
|
||||
|
||||
xcb_window_t get_focus(void);
|
||||
xcb_window_t get_root(void);
|
||||
|
||||
void focus(xcb_window_t win);
|
||||
void subscribe(xcb_window_t win);
|
||||
void kill(xcb_window_t window);
|
||||
void destroy(xcb_window_t win);
|
||||
void show(xcb_window_t window);
|
||||
void hide(xcb_window_t window);
|
||||
void send_to_top(xcb_window_t win);
|
||||
|
||||
Geometry get_geometry(xcb_window_t win);
|
||||
PointerInfo get_pointer(void);
|
||||
Geometry get_screen(void);
|
||||
void free_geometry(Geometry *g);
|
||||
|
||||
void warp_pointer(xcb_window_t win, int x, int y);
|
||||
void move_window(xcb_window_t win, int x, int y);
|
||||
void resize_window(xcb_window_t win, int width, int height);
|
||||
|
||||
xcb_size_hints_t get_wm_n_hints(xcb_window_t win);
|
||||
xcb_icccm_wm_hints_t get_wm_hints(xcb_window_t win);
|
||||
char *get_wm_name(xcb_window_t win);
|
||||
uint8_t get_wm_transient_for(xcb_window_t win);
|
||||
void set_wm_state(xcb_window_t win, int state);
|
||||
|
||||
xcb_window_t draw_rectangle(int x, int y, int width, int height,
|
||||
uint32_t color);
|
||||
|
||||
void grab_pointer(xcb_window_t win);
|
||||
void ungrab_pointer(void);
|
||||
|
||||
xcb_generic_event_t *next_event(void);
|
||||
Event translate_event(xcb_generic_event_t *ev);
|
||||
|
||||
extern inline int mod() { return MOD; }
|
||||
extern inline int clk() { return XCB_MOD_MASK_LOCK; }
|
||||
extern inline int num() { return XCB_MOD_MASK_2; }
|
||||
|
||||
#endif // X_KUTU_H
|
||||
-13
@@ -1,13 +0,0 @@
|
||||
class Node
|
||||
attr_accessor :size
|
||||
|
||||
def initialize
|
||||
@size = 0
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Require submodules
|
||||
|
||||
require_relative "./window"
|
||||
require_relative "./window_block"
|
||||
@@ -2,7 +2,7 @@ require "ffi"
|
||||
|
||||
module X
|
||||
extend FFI::Library
|
||||
ffi_lib File.join(__dir__, "../build/X-kutu.so")
|
||||
ffi_lib File.join(__dir__, "../../build/X-kutu.so")
|
||||
|
||||
typedef :uint32, :xcb_window_t
|
||||
typedef :uint32, :xcb_pixmap_t
|
||||
@@ -14,6 +14,12 @@ module X
|
||||
:height, :uint16
|
||||
end
|
||||
|
||||
class PointerInfo < FFI::Struct
|
||||
layout :x, :int16,
|
||||
:y, :int16,
|
||||
:window, :xcb_window_t
|
||||
end
|
||||
|
||||
class Event < FFI::Struct
|
||||
layout :type, :int32,
|
||||
:window, :xcb_window_t,
|
||||
@@ -60,9 +66,10 @@ module X
|
||||
:window_group, :xcb_window_t
|
||||
end
|
||||
|
||||
attach_function :flush, [], :void
|
||||
attach_function :deploy, [], :int
|
||||
attach_function :add_keybind, [:int], :void
|
||||
attach_function :add_mousebind, [:int], :void
|
||||
attach_function :add_keybind, [:int, :int], :void
|
||||
attach_function :add_mousebind, [:int, :int], :void
|
||||
attach_function :cleanup, [], :void
|
||||
attach_function :focus, [:xcb_window_t], :void
|
||||
attach_function :get_focus, [], :xcb_window_t
|
||||
@@ -74,12 +81,13 @@ module X
|
||||
attach_function :send_to_top, [:xcb_window_t], :void
|
||||
attach_function :free_geometry, [:pointer], :void
|
||||
attach_function :get_geometry, [:xcb_window_t], Geometry.by_value
|
||||
attach_function :get_pointer, [], Geometry.by_value
|
||||
attach_function :get_pointer, [], PointerInfo.by_value
|
||||
attach_function :get_screen, [], Geometry.by_value
|
||||
attach_function :warp_pointer, [:xcb_window_t, :int, :int], :void
|
||||
attach_function :move_window, [:xcb_window_t, :int, :int], :void
|
||||
attach_function :resize_window, [:xcb_window_t, :int, :int], :void
|
||||
attach_function :wait_for_event, [], Event.by_value
|
||||
attach_function :translate_event, [:pointer], Event.by_value
|
||||
attach_function :next_event, [], :pointer
|
||||
attach_function :get_wm_name, [:xcb_window_t], :string
|
||||
attach_function :get_wm_n_hints, [:xcb_window_t], SizeHints.by_value
|
||||
attach_function :get_wm_hints, [:xcb_window_t], WMHints.by_value
|
||||
@@ -89,4 +97,7 @@ module X
|
||||
attach_function :grab_pointer, [:xcb_window_t], :void
|
||||
attach_function :ungrab_pointer, [], :void
|
||||
attach_function :get_root, [], :xcb_window_t
|
||||
attach_function :mod, [], :int
|
||||
attach_function :clk, [], :int
|
||||
attach_function :num, [], :int
|
||||
end
|
||||
@@ -0,0 +1,27 @@
|
||||
def handle_command(command)
|
||||
reply = {}
|
||||
|
||||
command_parts = command.split(" ")
|
||||
command = command_parts[0]
|
||||
args = command_parts[1..-1]
|
||||
|
||||
case command
|
||||
when "get-ws"
|
||||
monitor = args[0] ? $monitors[args[0].to_sym] : current_monitor
|
||||
return reply if monitor.nil?
|
||||
reply[:workspace] = monitor[:selected_workspace]
|
||||
reply[:monitor] = $monitors.key(monitor)
|
||||
reply[:count] = monitor[:workspaces].length
|
||||
when "set-ws"
|
||||
monitor = args[1] ? $monitors[args[1].to_sym] : current_monitor
|
||||
return reply if monitor.nil?
|
||||
select_workspace args[0].to_i, monitor
|
||||
reply[:workspace] = monitor[:selected_workspace]
|
||||
reply[:monitor] = $monitors.key(monitor)
|
||||
reply[:count] = monitor[:workspaces].length
|
||||
when "stop"
|
||||
exit 1
|
||||
end
|
||||
|
||||
reply
|
||||
end
|
||||
@@ -0,0 +1,24 @@
|
||||
def config_path
|
||||
File.join(
|
||||
ENV["XDG_CONFIG_HOME"] || File.expand_path("~/.config"),
|
||||
"kutu",
|
||||
"config.rb"
|
||||
)
|
||||
end
|
||||
|
||||
def load_config!
|
||||
path = config_path
|
||||
|
||||
unless File.exist?(path)
|
||||
load "../src/ruby/defaults.rb"
|
||||
return
|
||||
end
|
||||
|
||||
puts "loading kutu config: #{path}"
|
||||
|
||||
load path
|
||||
rescue Exception => e
|
||||
warn "failed to load kutu config"
|
||||
warn "#{e.class}: #{e.message}"
|
||||
warn e.backtrace.join("\n")
|
||||
end
|
||||
@@ -0,0 +1,25 @@
|
||||
def create_workspace(monitor)
|
||||
monitor[:workspaces] << Workspace.new($monitors.key(monitor))
|
||||
monitor[:selected_workspace] = monitor[:workspaces].length - 1
|
||||
end
|
||||
|
||||
def delete_workspace(n, monitor)
|
||||
return if monitor[:workspaces].length <= 2
|
||||
monitor[:workspaces][n].windows.each { |w| monitor[:workspaces][(n - 1) % monitor[:workspaces].length].drop 0, w }
|
||||
monitor[:workspaces].delete_at n
|
||||
if monitor[:selected_workspace] >= n
|
||||
monitor[:selected_workspace] -= 1
|
||||
end
|
||||
end
|
||||
|
||||
def select_workspace(n, monitor)
|
||||
if n >= monitor[:workspaces].length
|
||||
select_workspace monitor[:workspaces].length - 1, monitor
|
||||
elsif n < 0
|
||||
select_workspace 1, monitor
|
||||
end
|
||||
monitor[:workspaces].each { |w| w.hide if w != monitor[:workspaces][n] }
|
||||
monitor[:selected_workspace] = n
|
||||
monitor[:workspaces][n].show
|
||||
monitor[:workspaces][n].compute_tiled!
|
||||
end
|
||||
@@ -0,0 +1,89 @@
|
||||
bind_key 23, true, "firefox"
|
||||
bind_key 24, true, :kill_window
|
||||
bind_key 25, true, "kitty"
|
||||
bind_key 26, true, "bash /home/me/dotfiles/scripts/power.sh"
|
||||
|
||||
bind_key 123, false, %q(
|
||||
pactl set-sink-volume @DEFAULT_SINK@ +5%;
|
||||
vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\+%' | head -n1);
|
||||
dunstify "Volume Adjusted" "" -h int:value:$vol -r 997
|
||||
)
|
||||
|
||||
bind_key 122, false, %q(
|
||||
pactl set-sink-volume @DEFAULT_SINK@ -5%;
|
||||
vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\+%' | head -n1);
|
||||
dunstify "Volume Adjusted" "" -h int:value:$vol -r 997
|
||||
)
|
||||
|
||||
bind_key 121, false, %q(
|
||||
pactl set-sink-mute @DEFAULT_SINK@ toggle;
|
||||
vol=$(pactl get-sink-volume @DEFAULT_SINK@ | grep -o '[0-9]\+%' | head -n1);
|
||||
dunstify "Volume Adjusted" "" -h int:value:$vol -r 997
|
||||
)
|
||||
|
||||
bind_key 38, true, %Q(
|
||||
maim -c 0.3,0.5,1.0,0.8 -s | tee /tmp/screenshot_temp.png | xclip -selection clipboard -t image/png;
|
||||
if [ -s '/tmp/screenshot_temp.png' ]; then
|
||||
mv /tmp/screenshot_temp.png ~/screenshots/$(date +%Y-%m-%d_%H:%M:%S).png;
|
||||
fi
|
||||
)
|
||||
|
||||
bind_key 232, false, %q(
|
||||
brightnessctl set 5%-;
|
||||
pct=$(brightnessctl -m | cut -d, -f4 | tr -d ' %');
|
||||
dunstify "Brightness" "" -h int:value:$pct -r 998
|
||||
)
|
||||
|
||||
bind_key 233, false, %q(
|
||||
brightnessctl set 5%+;
|
||||
pct=$(brightnessctl -m | cut -d, -f4 | tr -d ' %');
|
||||
dunstify "Brightness" "" -h int:value:$pct -r 998
|
||||
)
|
||||
|
||||
bind_key 54, true, 'kitty -e fish -c "y"'
|
||||
bind_key 53, true, 'kitty -e fish -c "editor"'
|
||||
bind_key 41, true, 'kitty -e fish -c "btop"'
|
||||
|
||||
bind_key 40, true, "bash /home/me/dotfiles/scripts/run.sh"
|
||||
|
||||
bind_key 56, true, :add_workspace
|
||||
bind_key 57, true, :delete_workspace
|
||||
|
||||
bind_key 110, false, %q(
|
||||
dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify \
|
||||
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Pause;
|
||||
dbus-send --print-reply --dest=$(busctl --user list | grep -oP 'org.mpris.MediaPlayer2.firefox.instance_1_\d+') \
|
||||
/org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause
|
||||
)
|
||||
|
||||
bind_key 173, true,
|
||||
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Previous"
|
||||
|
||||
bind_key 171, true,
|
||||
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.Next"
|
||||
|
||||
bind_key 89, true, :toggle_scratchpad
|
||||
|
||||
bind_key 81, true, :toggle_move_scratchpad
|
||||
|
||||
bind_key 118, false,
|
||||
"dbus-send --print-reply --dest=org.mpris.MediaPlayer2.spotify /org/mpris/MediaPlayer2 org.mpris.MediaPlayer2.Player.PlayPause"
|
||||
|
||||
bind_key 86, false, :workspace_next
|
||||
bind_key 86, true, :move_window_next_workspace
|
||||
|
||||
bind_key 82, false, :workspace_previous
|
||||
bind_key 82, true, :move_window_previous_workspace
|
||||
|
||||
bind_key 10, true, "setxkbmap us"
|
||||
bind_key 11, true, "setxkbmap ara"
|
||||
|
||||
bind_key 55, true, "CM_LAUNCHER=rofi clipmenu"
|
||||
|
||||
bind_key 39, true, :toggle_floating
|
||||
|
||||
bind_mouse 9, false, :workspace_next
|
||||
bind_mouse 9, true, :move_window_next_workspace
|
||||
|
||||
bind_mouse 8, false, :workspace_previous
|
||||
bind_mouse 8, true, :move_window_previous_workspace
|
||||
@@ -9,50 +9,58 @@ EVENT_TYPES = {
|
||||
8 => :mouse_release,
|
||||
9 => :key_press,
|
||||
10 => :key_release,
|
||||
11 => :closed,
|
||||
11 => :closed_temp,
|
||||
12 => :configure_request,
|
||||
13 => :resize_request
|
||||
}.freeze
|
||||
|
||||
|
||||
def handle_event(event)
|
||||
case EVENT_TYPES[event[:type]]
|
||||
|
||||
|
||||
when :create
|
||||
$all_windows << event[:window]
|
||||
return unless event[:override_redirect].zero?
|
||||
X.subscribe event[:window]
|
||||
|
||||
|
||||
when :closed
|
||||
$all_windows.delete event[:window]
|
||||
$windows[event[:window]]&.delete
|
||||
$workspaces[:main].tiled_root_block.compute_geometry!
|
||||
X.focus $root
|
||||
X.flush
|
||||
mouse_pos = X.get_pointer
|
||||
X.focus mouse_pos[:window] if mouse_pos[:window] != 0
|
||||
|
||||
when :closed_temp
|
||||
X.focus $root
|
||||
X.flush
|
||||
mouse_pos = X.get_pointer
|
||||
X.focus mouse_pos[:window] if mouse_pos[:window] != 0
|
||||
|
||||
|
||||
when :enter
|
||||
X.send_to_top event[:window]
|
||||
floating_windows = $windows.select { |_i, w| w.floating }
|
||||
floating_windows.each { |_i, w| X.send_to_top w.window_id }
|
||||
X.focus event[:window]
|
||||
|
||||
|
||||
when :showed
|
||||
return unless event[:override_redirect].zero?
|
||||
X.send_to_top event[:window]
|
||||
floating_windows = $windows.select { |_i, w| w.floating }
|
||||
floating_windows.each { |_i, w| X.send_to_top w.window_id }
|
||||
X.focus event[:window]
|
||||
|
||||
|
||||
when :show_request
|
||||
$workspaces[:main].add event[:window] if $windows[event[:window]].nil?
|
||||
$workspaces[:main].tiled_root_block.compute_geometry!
|
||||
X.show event[:window]
|
||||
monitor = current_monitor
|
||||
if $windows[event[:window]].nil?
|
||||
monitor[:workspaces][monitor[:selected_workspace]].create event[:window]
|
||||
X.show event[:window]
|
||||
$all_windows.each do |window|
|
||||
above = `xprop -id #{window} _NET_WM_STATE`
|
||||
X.send_to_top window if above.include?("ABOVE")
|
||||
end
|
||||
end
|
||||
X.focus event[:window]
|
||||
|
||||
|
||||
when :mouse_press
|
||||
$mousebind_actions[[event[:btn], event[:state]]]&.call(event)
|
||||
return if event[:is_root] != 0
|
||||
return if $windows[event[:window]].nil?
|
||||
return if event[:state] != 1
|
||||
$mouse_data[:btn] = event[:btn]
|
||||
$mouse_data[:window] = $windows[event[:window]]
|
||||
X.grab_pointer $root
|
||||
@@ -63,7 +71,6 @@ def handle_event(event)
|
||||
else
|
||||
$mouse_data[:mode] = :tiled
|
||||
end
|
||||
$mousebind_actions[event[:btn]]&.call(event)
|
||||
|
||||
when :mouse_drag
|
||||
mouse_pos = X.get_pointer
|
||||
@@ -73,13 +80,14 @@ def handle_event(event)
|
||||
if $mouse_data[:btn] == 1
|
||||
$mouse_data[:window].move $mouse_data[:geometry][:x] + dx, $mouse_data[:geometry][:y] + dy
|
||||
elsif $mouse_data[:btn] == 3
|
||||
X.resize_window $mouse_data[:window].window_id,
|
||||
[$mouse_data[:geometry][:width] + dx, 50].max,
|
||||
[$mouse_data[:geometry][:height] + dy, 50].max
|
||||
$mouse_data[:window]&.resize [$mouse_data[:geometry][:width] + dx, 50].max,
|
||||
[$mouse_data[:geometry][:height] + dy, 50].max,
|
||||
true
|
||||
end
|
||||
elsif $mouse_data[:mode] == :tiled
|
||||
if $mouse_data[:btn] == 1
|
||||
find_targets = compute_drop_targets! $mouse_data[:window]
|
||||
monitor = current_monitor
|
||||
find_targets = monitor[:workspaces][monitor[:selected_workspace]].insertion_rects
|
||||
target = drop_target(find_targets, mouse_pos[:x], mouse_pos[:y])
|
||||
if target
|
||||
if $rect[:target] != target
|
||||
@@ -98,17 +106,21 @@ def handle_event(event)
|
||||
|
||||
when :mouse_release
|
||||
if $mouse_data[:mode] == :tiled
|
||||
X.destroy $rect[:id] if $rect[:id]
|
||||
$rect = {}
|
||||
if $mouse_data[:btn] == 1
|
||||
if $rect[:id]
|
||||
X.destroy $rect[:id]
|
||||
monitor = current_monitor
|
||||
monitor[:workspaces][monitor[:selected_workspace]].drop $rect[:target][:drop], $mouse_data[:window]
|
||||
end
|
||||
$rect = {}
|
||||
end
|
||||
end
|
||||
X.ungrab_pointer
|
||||
X.send_to_top $mouse_data[:window].window_id if $mouse_data[:window]
|
||||
X.focus $mouse_data[:window].window_id if $mouse_data[:window]
|
||||
$mouse_data = {}
|
||||
|
||||
|
||||
when :key_press
|
||||
$keybind_actions[event[:btn]]&.call(event)
|
||||
$keybind_actions[[event[:btn], event[:state]]]&.call(event)
|
||||
|
||||
when :key_release
|
||||
# TODO
|
||||
@@ -116,12 +128,8 @@ def handle_event(event)
|
||||
|
||||
when :configure_request
|
||||
$windows[event[:window]]&.resize event[:width], event[:height]
|
||||
X.send_to_top event[:window]
|
||||
X.focus event[:window]
|
||||
|
||||
when :resize_request
|
||||
$windows[event[:window]]&.resize event[:width], event[:height]
|
||||
X.send_to_top event[:window]
|
||||
X.focus event[:window]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,179 @@
|
||||
PERSISTENCE_PATH = File.join(
|
||||
ENV["XDG_STATE_HOME"] || File.expand_path("~/.local/state"),
|
||||
"kutu",
|
||||
"num.json"
|
||||
)
|
||||
|
||||
ACTIONS = {}
|
||||
|
||||
def action(name, &block)
|
||||
ACTIONS[name] = block
|
||||
end
|
||||
|
||||
action :kill_window do |event|
|
||||
X.kill event[:window]
|
||||
end
|
||||
|
||||
action :toggle_floating do |event|
|
||||
window = $windows[event[:window]]
|
||||
window.toggle_floating if window
|
||||
end
|
||||
|
||||
action :workspace_next do |_event|
|
||||
monitor = current_monitor
|
||||
next if monitor[:selected_workspace].zero?
|
||||
|
||||
next_ws =
|
||||
(monitor[:selected_workspace] %
|
||||
(monitor[:workspaces].length - 1)) + 1
|
||||
|
||||
select_workspace next_ws, monitor
|
||||
end
|
||||
|
||||
action :workspace_previous do |_event|
|
||||
monitor = current_monitor
|
||||
next if monitor[:selected_workspace].zero?
|
||||
|
||||
next_ws =
|
||||
((monitor[:selected_workspace] - 2) %
|
||||
(monitor[:workspaces].length - 1)) + 1
|
||||
|
||||
select_workspace next_ws, monitor
|
||||
end
|
||||
|
||||
action :move_window_next_workspace do |event|
|
||||
monitor = current_monitor
|
||||
next if monitor[:selected_workspace].zero?
|
||||
|
||||
ws =
|
||||
monitor[:workspaces][
|
||||
(monitor[:selected_workspace] %
|
||||
(monitor[:workspaces].length - 1)) + 1
|
||||
]
|
||||
|
||||
ws.drop ws.tiled_windows.length,
|
||||
$windows[event[:window]] if $windows[event[:window]]
|
||||
end
|
||||
|
||||
action :move_window_previous_workspace do |event|
|
||||
monitor = current_monitor
|
||||
next if monitor[:selected_workspace].zero?
|
||||
|
||||
ws =
|
||||
monitor[:workspaces][
|
||||
((monitor[:selected_workspace] - 2) % (monitor[:workspaces].length - 1)) + 1
|
||||
]
|
||||
|
||||
ws.drop ws.tiled_windows.length,
|
||||
$windows[event[:window]] if $windows[event[:window]]
|
||||
end
|
||||
|
||||
action :toggle_scratchpad do |_event|
|
||||
monitor = current_monitor
|
||||
persistence = File.exist?(PERSISTENCE_PATH) ?
|
||||
JSON.parse(File.read(PERSISTENCE_PATH), symbolize_names: true) :
|
||||
{}
|
||||
if monitor[:selected_workspace] == 0
|
||||
select_workspace persistence[$monitors.key(monitor)]&.[](:saved) || 1, monitor
|
||||
else
|
||||
persistence[$monitors.key(monitor)] ||= {}
|
||||
persistence[$monitors.key(monitor)][:saved] = monitor[:selected_workspace]
|
||||
File.write(PERSISTENCE_PATH, JSON.pretty_generate(persistence))
|
||||
select_workspace 0, monitor
|
||||
end
|
||||
end
|
||||
|
||||
action :toggle_move_scratchpad do |event|
|
||||
monitor = current_monitor
|
||||
persistence = File.exist?(PERSISTENCE_PATH) ?
|
||||
JSON.parse(File.read(PERSISTENCE_PATH), symbolize_names: true) :
|
||||
{}
|
||||
ws = monitor[:workspaces][
|
||||
monitor[:selected_workspace] == 0 ?
|
||||
monitor[:workspaces][persistence[$monitors.key(monitor)]&.[](:saved) || 1] : 0
|
||||
]
|
||||
ws.drop ws.tiled_windows.length, $windows[event[:window]] if $windows[event[:window]]
|
||||
end
|
||||
|
||||
action :add_workspace do |_event|
|
||||
monitor = current_monitor
|
||||
create_workspace monitor
|
||||
persistence = File.exist?(PERSISTENCE_PATH) ?
|
||||
JSON.parse(File.read(PERSISTENCE_PATH), symbolize_names: true) :
|
||||
{}
|
||||
persistence[$monitors.key(monitor)] ||= {}
|
||||
persistence[$monitors.key(monitor)][:length] = monitor[:workspaces].length
|
||||
File.write(PERSISTENCE_PATH, JSON.pretty_generate(persistence))
|
||||
end
|
||||
|
||||
action :delete_workspace do |_event|
|
||||
monitor = current_monitor
|
||||
delete_workspace monitor[:workspaces].length - 1, monitor
|
||||
persistence = File.exist?(PERSISTENCE_PATH) ?
|
||||
JSON.parse(File.read(PERSISTENCE_PATH), symbolize_names: true) :
|
||||
{}
|
||||
persistence[$monitors.key(monitor)] ||= {}
|
||||
persistence[$monitors.key(monitor)][:length] = monitor[:workspaces].length
|
||||
File.write(PERSISTENCE_PATH, JSON.pretty_generate(persistence))
|
||||
end
|
||||
|
||||
PRESETS = {
|
||||
normal: [0, X.clk, X.num | X.clk, X.num],
|
||||
mod: [
|
||||
X.mod,
|
||||
X.mod | X.clk,
|
||||
X.num | X.clk | X.mod,
|
||||
X.num | X.mod
|
||||
]
|
||||
}
|
||||
|
||||
|
||||
def resolve_action(action, &block)
|
||||
action ||= block
|
||||
|
||||
case action
|
||||
when Symbol
|
||||
ACTIONS.fetch(action) do
|
||||
raise "Unknown kutu action: #{action}"
|
||||
end
|
||||
|
||||
when String
|
||||
proc do |_event|
|
||||
run action
|
||||
end
|
||||
|
||||
when Proc
|
||||
action
|
||||
|
||||
else
|
||||
raise ArgumentError,
|
||||
"Expected action Symbol, String, or Block, got #{action.class}"
|
||||
end
|
||||
end
|
||||
|
||||
PRESETS[:mod].each do |modifier|
|
||||
X.add_mousebind 1, modifier
|
||||
end
|
||||
|
||||
PRESETS[:mod].each do |modifier|
|
||||
X.add_mousebind 3, modifier
|
||||
end
|
||||
|
||||
def bind_key(code, mod, action = nil, &block)
|
||||
callback = resolve_action(action, &block)
|
||||
|
||||
PRESETS[mod ? :mod : :normal].each do |modifier|
|
||||
X.add_keybind code, modifier
|
||||
$keybind_actions[[code, modifier]] = callback
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def bind_mouse(button, mod, action = nil, &block)
|
||||
callback = resolve_action(action, &block)
|
||||
|
||||
PRESETS[mod ? :mod : :normal].each do |modifier|
|
||||
X.add_mousebind button, modifier
|
||||
$mousebind_actions[[button, modifier]] = callback
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,65 @@
|
||||
PERSISTENCE_PATH = File.join(
|
||||
ENV["XDG_STATE_HOME"] || File.expand_path("~/.local/state"),
|
||||
"kutu",
|
||||
"num.json"
|
||||
)
|
||||
|
||||
def run(cmd)
|
||||
pid = spawn "bash", "-c", cmd
|
||||
Process.detach pid
|
||||
end
|
||||
|
||||
|
||||
|
||||
def load_monitors!
|
||||
$monitors.clear
|
||||
xrandr_output = `xrandr --query`
|
||||
connected = xrandr_output.each_line.select { |line| line.include?(" connected") }
|
||||
connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 }
|
||||
persistence = File.exist?(PERSISTENCE_PATH) ?
|
||||
JSON.parse(File.read(PERSISTENCE_PATH), symbolize_names: true) :
|
||||
{}
|
||||
connected.first(2).each_with_index do |line, index|
|
||||
next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/
|
||||
w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i
|
||||
key = index.zero? ? :primary : :secondary
|
||||
$monitors[key] = {
|
||||
x: x, y: y, width: w, height: h,
|
||||
workspaces: Array.new(persistence[key]&.[](:length) || 2) { Workspace.new(key) },
|
||||
selected_workspace: 1
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
def current_monitor(pointer = X.get_pointer)
|
||||
$monitors.find do |_, r|
|
||||
pointer[:x] >= r[:x] &&
|
||||
pointer[:x] < r[:x] + r[:width] &&
|
||||
pointer[:y] >= r[:y] &&
|
||||
pointer[:y] < r[:y] + r[:height]
|
||||
end&.last
|
||||
end
|
||||
|
||||
|
||||
|
||||
def fixed_size_or_aspect?(hints)
|
||||
return false unless hints
|
||||
flags = hints[:flags] || 0
|
||||
same_size = (flags & (16 | 256) != 0) && (flags & 32 != 0) &&
|
||||
hints[:max_width] == (hints[:min_width] || hints[:base_width]) &&
|
||||
hints[:max_height] == (hints[:min_height] || hints[:base_height])
|
||||
fixed_aspect = (flags & 128 != 0) &&
|
||||
hints[:min_aspect_num] == hints[:max_aspect_num] &&
|
||||
hints[:min_aspect_den] == hints[:max_aspect_den] &&
|
||||
hints[:min_aspect_num] != 0
|
||||
same_size || fixed_aspect
|
||||
end
|
||||
|
||||
def drop_target(targets, cx, cy)
|
||||
targets.find do |t|
|
||||
cx >= t[:x] &&
|
||||
cy >= t[:y] &&
|
||||
cx < t[:x] + t[:width] &&
|
||||
cy < t[:y] + t[:height]
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,90 @@
|
||||
def godot_window?(window)
|
||||
`xprop -id #{window} WM_CLASS`.downcase.include?("godot")
|
||||
end
|
||||
|
||||
class Window
|
||||
attr_accessor :window_id, :floating, :workspace, :def_floating, :size, :x, :y, :width, :height
|
||||
|
||||
def initialize(window_id, workspace)
|
||||
@size = 0
|
||||
@workspace = workspace
|
||||
@window_id = window_id
|
||||
geometry = X.get_geometry(window_id)
|
||||
@x = geometry[:x]
|
||||
@y = geometry[:y]
|
||||
@width = geometry[:width]
|
||||
@height = geometry[:height]
|
||||
wm_n_hints = X.get_wm_n_hints(window_id)
|
||||
@floating = godot_window?(window_id)
|
||||
@def_floating = @floating
|
||||
if wm_n_hints
|
||||
if fixed_size_or_aspect?(wm_n_hints)
|
||||
@floating = true
|
||||
@def_floating = true
|
||||
@width = wm_n_hints[:max_width]
|
||||
if wm_n_hints[:flags] & 128 != 0
|
||||
@height = wm_n_hints[:max_width] * (wm_n_hints[:min_aspect_num] / wm_n_hints[:min_aspect_den])
|
||||
else
|
||||
@height = wm_n_hints[:max_height]
|
||||
end
|
||||
@x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2
|
||||
@y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2
|
||||
apply_geometry!
|
||||
end
|
||||
end
|
||||
X.set_wm_state window_id, 1
|
||||
transient_for = X.get_wm_transient_for(window_id)
|
||||
if !transient_for.zero? && !godot_window?(window_id)
|
||||
@floating = true
|
||||
@def_floating = true
|
||||
@width = wm_n_hints[:max_width]
|
||||
if wm_n_hints[:flags] & 128 != 0
|
||||
@height = wm_n_hints[:max_width] * (wm_n_hints[:min_aspect_num] / wm_n_hints[:min_aspect_den])
|
||||
else
|
||||
@height = wm_n_hints[:max_height]
|
||||
end
|
||||
@x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2
|
||||
@y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2
|
||||
apply_geometry!
|
||||
end
|
||||
super()
|
||||
end
|
||||
|
||||
def delete
|
||||
@workspace.remove self
|
||||
end
|
||||
|
||||
def each_leaf(&block)
|
||||
block.call(self)
|
||||
end
|
||||
|
||||
def toggle_floating
|
||||
if !@def_floating
|
||||
@floating = !@floating
|
||||
@workspace.check_floating self
|
||||
apply_geometry!
|
||||
end
|
||||
end
|
||||
|
||||
def move(x, y)
|
||||
return unless @floating
|
||||
@x, @y = x, y
|
||||
apply_geometry!
|
||||
end
|
||||
|
||||
def resize(width, height, force = false)
|
||||
return unless @floating
|
||||
@width, @height = width, height
|
||||
if !force
|
||||
@x = $monitors[@workspace.monitor_id][:width] / 2 - @width / 2
|
||||
@y = $monitors[@workspace.monitor_id][:height] / 2 - @height / 2
|
||||
end
|
||||
apply_geometry!
|
||||
end
|
||||
|
||||
def apply_geometry!
|
||||
X.send_to_top @window_id if @floating
|
||||
X.move_window @window_id, @x, @y
|
||||
X.resize_window @window_id, @width, @height
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,161 @@
|
||||
class Workspace
|
||||
attr_reader :monitor_id
|
||||
attr_accessor :tiled_windows, :untiled_windows
|
||||
|
||||
def initialize(monitor_id = :primary, direction = :horizontal)
|
||||
@monitor_id = monitor_id
|
||||
@direction = direction
|
||||
@tiled_windows = []
|
||||
@untiled_windows = []
|
||||
end
|
||||
|
||||
def drop(idx_dst, window)
|
||||
idx_src = window.workspace.tiled_windows.index(window)
|
||||
window.workspace.tiled_windows.delete_at idx_src
|
||||
self.tiled_windows.insert idx_dst, window
|
||||
window.workspace.compute_tiled! if window.workspace != self
|
||||
window.workspace = self
|
||||
compute_tiled!
|
||||
select_workspace $monitors[@monitor_id][:selected_workspace], $monitors[@monitor_id]
|
||||
end
|
||||
|
||||
def insertion_rects
|
||||
rects = []
|
||||
return rects if @tiled_windows.empty?
|
||||
compute_tiled!
|
||||
|
||||
horizontal = (@direction == :horizontal)
|
||||
z = 100
|
||||
half = z / 2
|
||||
|
||||
wins = @tiled_windows
|
||||
|
||||
first = wins.first
|
||||
if horizontal
|
||||
rects << { x: first.x, y: first.y, width: z, height: first.height, drop: 0 }
|
||||
else
|
||||
rects << { x: first.x, y: first.y, width: first.width, height: z, drop: 0 }
|
||||
end
|
||||
|
||||
wins.each_cons(2).with_index do |(a, b), i|
|
||||
if horizontal
|
||||
mid = (a.x + a.width + b.x) / 2.0
|
||||
rects << {
|
||||
x: mid - half,
|
||||
y: a.y,
|
||||
width: z,
|
||||
height: a.height,
|
||||
drop: i
|
||||
}
|
||||
else
|
||||
mid = (a.y + a.height + b.y) / 2.0
|
||||
rects << {
|
||||
x: a.x,
|
||||
y: mid - half,
|
||||
width: a.width,
|
||||
height: z,
|
||||
drop: i
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
last = wins.last
|
||||
if horizontal
|
||||
rects << { x: last.x + last.width - z, y: last.y, width: z, height: last.height, drop: wins.count - 1 }
|
||||
else
|
||||
rects << { x: last.x, y: last.y + last.height - z, width: z, height: last.width, drop: wins.count - 1 }
|
||||
end
|
||||
|
||||
rects
|
||||
end
|
||||
|
||||
def windows
|
||||
@tiled_windows + @untiled_windows
|
||||
end
|
||||
|
||||
def check_floating(window)
|
||||
if window.floating
|
||||
@untiled_windows << window
|
||||
@tiled_windows.delete window
|
||||
else
|
||||
@tiled_windows << window
|
||||
@untiled_windows.delete window
|
||||
end
|
||||
compute_tiled!
|
||||
end
|
||||
|
||||
def switch_direction
|
||||
@direction = @direction == :horizontal ? :vertical : :horizontal
|
||||
windows.each { |window| window.size = 0 }
|
||||
compute_tiled!
|
||||
end
|
||||
|
||||
def create(window_id)
|
||||
window = Window.new(window_id, self)
|
||||
$windows[window_id] = window
|
||||
if window.floating
|
||||
@untiled_windows << window
|
||||
else
|
||||
@tiled_windows << window
|
||||
compute_tiled!
|
||||
end
|
||||
end
|
||||
|
||||
def remove(window)
|
||||
@untiled_windows.delete window
|
||||
@tiled_windows.delete window
|
||||
$windows.delete window.window_id
|
||||
compute_tiled!
|
||||
end
|
||||
|
||||
def compute_tiled!
|
||||
return if @tiled_windows.empty?
|
||||
|
||||
monitor = $monitors[@monitor_id]
|
||||
horizontal = @direction == :horizontal
|
||||
abs_total = @tiled_windows.sum { |c| c.size.to_i }
|
||||
flex_count = @tiled_windows.count { |c| c.size.to_i <= 0 }
|
||||
total_space = horizontal ? monitor[:width] : monitor[:height]
|
||||
remaining = total_space - abs_total
|
||||
remaining = 0 if remaining < 0
|
||||
flex_each = flex_count > 0 ? (remaining.to_f / flex_count) : 0
|
||||
pos = horizontal ? monitor[:x] : monitor[:y]
|
||||
|
||||
@tiled_windows.each do |window|
|
||||
px = window.size.to_i > 0 ? window.size.to_i : flex_each
|
||||
if horizontal
|
||||
window.x = pos
|
||||
window.y = monitor[:y]
|
||||
window.width = px
|
||||
window.height = monitor[:height]
|
||||
else
|
||||
window.x = monitor[:x]
|
||||
window.y = pos
|
||||
window.width = monitor[:width]
|
||||
window.height = px
|
||||
end
|
||||
window.apply_geometry!
|
||||
pos += px
|
||||
end
|
||||
end
|
||||
|
||||
def close_all
|
||||
self.windows.each { |window| remove window }
|
||||
end
|
||||
|
||||
def hide
|
||||
self.windows.each do |window|
|
||||
X.hide window.window_id
|
||||
X.set_wm_state window.window_id, 3
|
||||
end
|
||||
X.focus $root
|
||||
end
|
||||
|
||||
def show
|
||||
self.windows.each do |window|
|
||||
X.show window.window_id
|
||||
X.set_wm_state window.window_id, 1
|
||||
end
|
||||
X.focus $root
|
||||
end
|
||||
end
|
||||
@@ -1,81 +0,0 @@
|
||||
def keybind(key, &block)
|
||||
X.add_keybind key
|
||||
$keybind_actions[key] = block if block
|
||||
end
|
||||
|
||||
def mousebind(btn, &block)
|
||||
X.add_mousebind btn
|
||||
$mousebind_actions[btn] = block if block
|
||||
end
|
||||
|
||||
|
||||
|
||||
def refresh_monitors!
|
||||
$monitors.clear
|
||||
xrandr_output = `xrandr --query`
|
||||
connected = xrandr_output.each_line.select { |line| line.include?(" connected") }
|
||||
connected.sort_by! { |line| line.include?(" primary") ? 0 : 1 }
|
||||
connected.first(2).each_with_index do |line, index|
|
||||
next unless line =~ /(\d+)x(\d+)\+(\d+)\+(\d+)/
|
||||
w, h, x, y = $1.to_i, $2.to_i, $3.to_i, $4.to_i
|
||||
key = index.zero? ? :primary : :secondary
|
||||
$monitors[key] = { x: x, y: y, width: w, height: h }
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
|
||||
def fixed_size_or_aspect?(hints)
|
||||
return false unless hints
|
||||
flags = hints[:flags] || 0
|
||||
same_size = (flags & (16 | 256) != 0) && (flags & 32 != 0) &&
|
||||
hints[:max_width] == (hints[:min_width] || hints[:base_width]) &&
|
||||
hints[:max_height] == (hints[:min_height] || hints[:base_height])
|
||||
fixed_aspect = (flags & 128 != 0) &&
|
||||
hints[:min_aspect_num] == hints[:max_aspect_num] &&
|
||||
hints[:min_aspect_den] == hints[:max_aspect_den] &&
|
||||
hints[:min_aspect_num] != 0
|
||||
same_size || fixed_aspect
|
||||
end
|
||||
|
||||
|
||||
|
||||
def compute_drop_targets!(window)
|
||||
targets = []
|
||||
|
||||
$monitors.each_value do |monitor|
|
||||
# if monitor[:windows].nil? || monitor[:windows].empty?
|
||||
# targets << { type: :monitor_full, x: monitor[:x], y: monitor[:y], width: monitor[:width], height: monitor[:height], monitor: monitor }
|
||||
# else
|
||||
margin = 60
|
||||
targets << { type: :monitor_top, x: monitor[:x], y: monitor[:y], width: monitor[:width], height: margin, monitor: monitor }
|
||||
targets << { type: :monitor_bottom, x: monitor[:x], y: monitor[:y] + monitor[:height] - margin, width: monitor[:width], height: margin, monitor: monitor }
|
||||
targets << { type: :monitor_left, x: monitor[:x], y: monitor[:y], width: margin, height: monitor[:height], monitor: monitor }
|
||||
targets << { type: :monitor_right, x: monitor[:x] + monitor[:width] - margin, y: monitor[:y], width: margin, height: monitor[:height], monitor: monitor }
|
||||
# end
|
||||
end
|
||||
|
||||
$windows.each_value do |w|
|
||||
next if w.floating
|
||||
next if w == window
|
||||
|
||||
margin_x = (w.width / 4).round
|
||||
margin_y = (w.height / 4).round
|
||||
|
||||
targets << { type: :window_top, x: w.x, y: w.y, width: w.width, height: margin_y, window: w }
|
||||
targets << { type: :window_bottom, x: w.x, y: w.y + w.height - margin_y, width: w.width, height: margin_y, window: w }
|
||||
targets << { type: :window_left, x: w.x, y: w.y, width: margin_x, height: w.height, window: w }
|
||||
targets << { type: :window_right, x: w.x + w.width - margin_x, y: w.y, width: margin_x, height: w.height, window: w }
|
||||
end
|
||||
|
||||
targets
|
||||
end
|
||||
|
||||
def drop_target(targets, cx, cy)
|
||||
targets.find do |t|
|
||||
cx >= t[:x] &&
|
||||
cy >= t[:y] &&
|
||||
cx < t[:x] + t[:width] &&
|
||||
cy < t[:y] + t[:height]
|
||||
end
|
||||
end
|
||||
@@ -1,61 +0,0 @@
|
||||
class Window < Node
|
||||
attr_accessor :window_id, :x, :y, :width, :height, :state, :floating, :workspace
|
||||
|
||||
def initialize(window_id, workspace)
|
||||
@workspace = workspace
|
||||
@window_id = window_id
|
||||
@name = X.get_wm_name(window_id)
|
||||
@wm_n_hints = X.get_wm_n_hints(window_id)
|
||||
@floating = false
|
||||
if @wm_n_hints
|
||||
if fixed_size_or_aspect?(@wm_n_hints)
|
||||
@floating = true
|
||||
@width = @wm_n_hints[:max_width]
|
||||
if @wm_n_hints[:flags] & 128 != 0
|
||||
@height = @wm_n_hints[:max_width] * (@wm_n_hints[:min_aspect_num] / @wm_n_hints[:min_aspect_den])
|
||||
else
|
||||
@height = @wm_n_hints[:max_height]
|
||||
end
|
||||
@x = @workspace.width / 2 - @width / 2
|
||||
@y = @workspace.height / 2 - @height / 2
|
||||
apply_geometry!
|
||||
end
|
||||
end
|
||||
@wm_hints = X.get_wm_hints(window_id)
|
||||
if @wm_hints
|
||||
if @wm_hints[:flags] & 1 != 0 && @wm_hints[:initial_state] == 3
|
||||
X.set_wm_state window_id, 1
|
||||
end
|
||||
end
|
||||
@transient_for = X.get_wm_transient_for(window_id)
|
||||
@floating = true unless @transient_for.zero?
|
||||
super()
|
||||
end
|
||||
|
||||
def delete
|
||||
@workspace.remove self
|
||||
end
|
||||
|
||||
def each_leaf(&block)
|
||||
block.call(self)
|
||||
end
|
||||
|
||||
def move(x, y)
|
||||
return unless @floating
|
||||
@x, @y = x, y
|
||||
apply_geometry!
|
||||
end
|
||||
|
||||
def resize(width, height)
|
||||
return unless @floating
|
||||
@x = @workspace.width / 2 - width / 2
|
||||
@y = @workspace.height / 2 - height / 2
|
||||
@width, @height = width, height
|
||||
apply_geometry!
|
||||
end
|
||||
|
||||
def apply_geometry!
|
||||
X.move_window @window_id, @x, @y
|
||||
X.resize_window @window_id, @width, @height
|
||||
end
|
||||
end
|
||||
@@ -1,64 +0,0 @@
|
||||
class WindowBlock < Node
|
||||
attr_accessor :children, :direction, :size, :x, :y, :width, :height
|
||||
|
||||
def initialize(direction = :horizontal)
|
||||
@children = []
|
||||
@direction = direction
|
||||
super()
|
||||
end
|
||||
|
||||
def add_node(node)
|
||||
@children << node
|
||||
end
|
||||
|
||||
def each_leaf(&block)
|
||||
@children.each { |child| child.each_leaf(&block) }
|
||||
end
|
||||
|
||||
def each_node(&block)
|
||||
block.call(self)
|
||||
@children.each { |child| child.each_node(&block) if child.is_a? WindowBlock }
|
||||
end
|
||||
|
||||
def compute_geometry!
|
||||
return if @children.empty?
|
||||
|
||||
horizontal = @direction == :horizontal
|
||||
total_percent = @children.map { |c| c.size.to_i > 0 ? c.size.to_i : 0 }.sum
|
||||
flex_count = @children.count { |c| c.size.to_i <= 0 }
|
||||
total_space = horizontal ? @width : @height
|
||||
total_percent = [total_percent, 100].min
|
||||
remaining_percent = 100 - total_percent
|
||||
flex_percent = flex_count > 0 ? remaining_percent / flex_count : 0
|
||||
pos = horizontal ? @x : @y
|
||||
|
||||
@children.each do |child|
|
||||
percent = child.size.to_i > 0 ? child.size.to_i : flex_percent
|
||||
child_size = (total_space * percent) / 100.0
|
||||
if horizontal
|
||||
child.x = pos
|
||||
child.y = @y
|
||||
child.width = child_size
|
||||
child.height = @height
|
||||
else
|
||||
child.x = @x
|
||||
child.y = pos
|
||||
child.width = @width
|
||||
child.height = child_size
|
||||
end
|
||||
child.compute_geometry! if child.is_a?(WindowBlock)
|
||||
child.apply_geometry! if child.is_a?(Window)
|
||||
pos += child_size
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class RootWindowBlock < WindowBlock
|
||||
def initialize(workspace)
|
||||
super(:horizontal)
|
||||
@x = workspace.x
|
||||
@y = workspace.y
|
||||
@width = workspace.width
|
||||
@height = workspace.height
|
||||
end
|
||||
end
|
||||
@@ -1,52 +0,0 @@
|
||||
class Workspace
|
||||
attr_reader :name, :monitor, :x, :y, :width, :height, :tiled_root_block
|
||||
|
||||
def initialize(name, monitor = :primary)
|
||||
@monitor = monitor
|
||||
@x = $monitors[monitor][:x]
|
||||
@y = $monitors[monitor][:y]
|
||||
@width = $monitors[monitor][:width]
|
||||
@height = $monitors[monitor][:height]
|
||||
@tiled_root_block = RootWindowBlock.new(self)
|
||||
@untiled_windows = []
|
||||
@name = name
|
||||
end
|
||||
|
||||
def windows
|
||||
windows = []
|
||||
@untiled_windows.each { |w| windows << w }
|
||||
@tiled_root_block.each_leaf { |w| windows << w }
|
||||
windows
|
||||
end
|
||||
|
||||
def add(window_id)
|
||||
window = Window.new(window_id, self)
|
||||
if window.floating
|
||||
@untiled_windows << window
|
||||
else
|
||||
@tiled_root_block.add_node window
|
||||
end
|
||||
$windows[window_id] = window
|
||||
end
|
||||
|
||||
def remove(window)
|
||||
@untiled_windows.delete window
|
||||
@tiled_root_block.each_node { |node| node.children.delete window }
|
||||
$windows.delete window.window_id
|
||||
end
|
||||
|
||||
def close_all
|
||||
self.windows.each do |window|
|
||||
X.kill window.window_id
|
||||
remove window
|
||||
end
|
||||
end
|
||||
|
||||
def hide
|
||||
self.windows.each { |window| X.hide window.window_id }
|
||||
end
|
||||
|
||||
def show
|
||||
self.windows.each { |window| X.show window.window_id }
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user