summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJeff Carr <[email protected]>2024-11-24 06:57:05 -0600
committerJeff Carr <[email protected]>2024-11-24 06:57:05 -0600
commitcfd6fdadd756edccd0115563d759e37306b57e9c (patch)
tree4c4e040ef66da9944c3e1b7a262e0f258668b329
day 1v0.0.0
-rw-r--r--.gitignore7
-rw-r--r--Makefile35
-rw-r--r--devilspie/compat.h29
-rw-r--r--devilspie/config.c282
-rw-r--r--devilspie/config.h43
-rw-r--r--devilspie/devilspie2.c517
-rw-r--r--devilspie/error_strings.c117
-rw-r--r--devilspie/error_strings.h55
-rw-r--r--devilspie/intl.h28
-rw-r--r--devilspie/script.c352
-rw-r--r--devilspie/script.h42
-rw-r--r--devilspie/script_functions.c2659
-rw-r--r--devilspie/script_functions.h148
-rw-r--r--devilspie/xutils.c779
-rw-r--r--devilspie/xutils.h85
-rw-r--r--stuff.go69
16 files changed, 5247 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..a42ec94
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.swp
+go.mod
+go.sum
+
+files/
+
+xstartplacement
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..059bc3e
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,35 @@
+.PHONY: build
+
+VERSION = $(shell git describe --tags)
+BUILDTIME = $(shell date +%Y.%m.%d)
+
+build:
+ GO111MODULE=off go build \
+ -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
+
+verbose:
+ GO111MODULE=off go build -v -x \
+ -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
+
+install:
+ GO111MODULE=off go install \
+ -ldflags "-X main.VERSION=${VERSION} -X main.BUILDTIME=${BUILDTIME} -X gui.GUIVERSION=${VERSION}"
+
+# makes a .deb package
+debian:
+ rm -f ~/incoming/virtigo*deb
+ go-deb --no-gui --repo go.wit.com/apps/virtigo
+
+# autofixes your import headers in your golang files
+goimports:
+ goimports -w *.go
+
+# remake the go.mod and go.sum files
+redomod:
+ rm -f go.*
+ GO111MODULE= go mod init
+ GO111MODULE= go mod tidy
+
+clean:
+ rm -f go.*
+ rm -f virtigo*
diff --git a/devilspie/compat.h b/devilspie/compat.h
new file mode 100644
index 0000000..25ade54
--- /dev/null
+++ b/devilspie/compat.h
@@ -0,0 +1,29 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2021 Darren Salt
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HEADER_COMPAT_
+#define __HEADER_COMPAT_
+
+#if defined(__GNUC__) || defined(__clang__)
+# define ATTR_MALLOC __attribute__((malloc))
+#else
+# define ATTR_MALLOC
+#endif
+
+#endif /* __HEADER_COMPAT_ */
diff --git a/devilspie/config.c b/devilspie/config.c
new file mode 100644
index 0000000..029dad7
--- /dev/null
+++ b/devilspie/config.c
@@ -0,0 +1,282 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2013-2017 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <glib.h>
+
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+
+#include <glib/gi18n.h>
+
+#include <locale.h>
+
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include "script.h"
+#include "script_functions.h"
+
+#include "config.h"
+
+/**
+ *
+ */
+GSList *event_lists[W_NUM_EVENTS] = { NULL, NULL, NULL, NULL, NULL };
+const char *const event_names[W_NUM_EVENTS] = {
+ "window_open",
+ "window_close",
+ "window_focus",
+ "window_blur",
+ "window_name_change",
+};
+
+
+/**
+ * filename_list_sortfunc
+ * function to sort the inserted filenames, to be able to determine
+ * which order files are loaded.
+ */
+static gint filename_list_sortfunc(gconstpointer a,gconstpointer b)
+{
+ gchar *file1 = (gchar*)a;
+ gchar *file2 = (gchar*)b;
+
+ return g_ascii_strcasecmp(file1, file2);
+}
+
+
+/**
+ *
+ */
+static GSList *add_lua_file_to_list(GSList *list, gchar *filename)
+{
+ gchar *temp_filename = g_strdup(filename);
+
+ list=g_slist_insert_sorted(list,
+ temp_filename,
+ filename_list_sortfunc);
+
+ return list;
+}
+
+
+
+/**
+ *
+ */
+static GSList *get_table_of_strings(lua_State *luastate,
+ gchar *script_folder,
+ gchar *table_name)
+{
+ GSList *list = NULL;
+
+ if (luastate) {
+
+ lua_getglobal(luastate, table_name);
+
+ // Do we have a value?
+ if (lua_isnil(luastate, -1)) {
+ goto EXITPOINT;
+ }
+
+ // Is it a table?
+ if (!lua_istable(luastate, -1)) {
+ goto EXITPOINT;
+ }
+
+ lua_pushnil(luastate);
+
+ while(lua_next(luastate, -2)) {
+ if (lua_isstring(luastate, -1)) {
+ char *temp = (char *)lua_tostring(luastate, -1);
+
+ gchar *added_filename = g_build_path(G_DIR_SEPARATOR_S,
+ script_folder,
+ temp,
+ NULL);
+
+ list = add_lua_file_to_list(list, added_filename);
+ }
+ lua_pop(luastate, 1);
+ }
+ lua_pop(luastate, 1);
+ }
+
+EXITPOINT:
+
+ return list;
+}
+
+
+/**
+ * is_in_list
+ * Go through _one_ list, and check if the filename is in this list
+ */
+static gboolean is_in_list(GSList *list, gchar *filename)
+{
+ gboolean result = FALSE;
+
+ if (list) {
+ GSList *temp_list = list;
+
+ while (temp_list) {
+ gchar *list_filename = (gchar*)temp_list->data;
+ if (list_filename) {
+
+ if (g_ascii_strcasecmp(list_filename, filename) == 0) {
+ result = TRUE;
+ }
+ }
+ temp_list = temp_list->next;
+ }
+ }
+
+ return result;
+}
+
+
+/**
+ * is_in_any_list
+ * Go through our lists, and check if the file is already in any of them
+ */
+static gboolean is_in_any_list(gchar *filename)
+{
+ win_event_type i;
+
+ for (i=0; i < W_NUM_EVENTS; i++) {
+ if (is_in_list(event_lists[i], filename))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+
+/**
+ * load_config
+ * Load configuration from a file - From this we set up the lists of files
+ * which decides what script to load on what wnck event.
+ */
+int load_config(gchar *filename)
+{
+ lua_State *config_lua_state = NULL;
+ int result = 0;
+ const gchar *current_file = NULL;
+ GSList *temp_window_open_file_list = NULL;
+
+ // First get list of Lua files in folder - Then read variables from
+ // devilspie2.lua and put the files in the required lists.
+
+ gchar *script_folder = g_path_get_dirname(filename);
+
+ GDir *dir = g_dir_open(script_folder, 0, NULL);
+ if (!g_file_test(script_folder, G_FILE_TEST_IS_DIR)) {
+
+ printf("%s\n", _("script_folder isn't a folder."));
+ return -1;
+ }
+
+ int total_number_of_files = 0;
+
+ config_lua_state = init_script();
+
+ if (g_file_test(filename, G_FILE_TEST_EXISTS)) {
+
+ if (run_script(config_lua_state, filename) != 0) {
+ printf(_("Error: %s\n"), filename);
+ result = -1;
+ goto EXITPOINT;
+ }
+
+ event_lists[W_CLOSE] = get_table_of_strings(config_lua_state,
+ script_folder,
+ "scripts_window_close");
+ event_lists[W_FOCUS] = get_table_of_strings(config_lua_state,
+ script_folder,
+ "scripts_window_focus");
+ event_lists[W_BLUR] = get_table_of_strings(config_lua_state,
+ script_folder,
+ "scripts_window_blur");
+ event_lists[W_NAME_CHANGED] = get_table_of_strings(config_lua_state,
+ script_folder,
+ "scripts_window_name_change");
+ }
+
+ // add the files in the folder to our linked list
+ while ((current_file = g_dir_read_name(dir))) {
+
+ gchar *temp_filename = g_build_path(G_DIR_SEPARATOR_S,
+ script_folder,
+ current_file,
+ NULL);
+
+ // we only bother with *.lua in the folder
+ // we also ignore dot files
+ if (current_file[0] != '.' && g_str_has_suffix(current_file, ".lua")) {
+ if (!is_in_any_list(temp_filename)) {
+ temp_window_open_file_list =
+ add_lua_file_to_list(temp_window_open_file_list, temp_filename);
+ }
+ total_number_of_files++;
+ }
+
+ g_free(temp_filename);
+ }
+
+ event_lists[W_OPEN] = temp_window_open_file_list;
+EXITPOINT:
+ if (config_lua_state)
+ done_script(config_lua_state);
+
+ return result;
+}
+
+
+
+/**
+ *
+ */
+static void unallocate_file_list(GSList *file_list)
+{
+ if (file_list) {
+ while(file_list) {
+ g_free ((gchar*)file_list->data);
+ file_list = file_list->next;
+ }
+ }
+}
+
+
+/**
+ *
+ */
+void clear_file_lists()
+{
+ win_event_type i = 0;
+
+ for (i = 0; i < W_NUM_EVENTS; i++) {
+ if (event_lists[i]) {
+ unallocate_file_list(event_lists[i]);
+ g_slist_free(event_lists[i]);
+ event_lists[i] = NULL;
+ }
+ }
+}
diff --git a/devilspie/config.h b/devilspie/config.h
new file mode 100644
index 0000000..6474226
--- /dev/null
+++ b/devilspie/config.h
@@ -0,0 +1,43 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2013-2017 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __HEADER_CONFIG_
+#define __HEADER_CONFIG_
+
+#include "glib.h"
+
+int load_config(gchar *config_filename);
+
+void clear_file_lists();
+
+typedef enum {
+ W_OPEN,
+ W_CLOSE,
+ W_FOCUS,
+ W_BLUR,
+ W_NAME_CHANGED,
+ W_NUM_EVENTS /* keep this at the end */
+} win_event_type;
+
+extern GSList *event_lists[W_NUM_EVENTS];
+extern const char *const event_names[W_NUM_EVENTS];
+
+// Our git version which is defined through some magic in the build system
+extern const char *gitversion;
+
+#endif /*__HEADER_CONFIG_*/
diff --git a/devilspie/devilspie2.c b/devilspie/devilspie2.c
new file mode 100644
index 0000000..cf9c994
--- /dev/null
+++ b/devilspie/devilspie2.c
@@ -0,0 +1,517 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2005 Ross Burton, 2011-2017 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <string.h>
+
+#include <stdlib.h>
+#include <glib.h>
+#include <glib/gstdio.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <glib/gi18n.h>
+
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <locale.h>
+
+#include "script.h"
+#include "script_functions.h"
+
+#include "error_strings.h"
+
+#include "config.h"
+
+
+#if (GTK_MAJOR_VERSION >= 3)
+#define HAVE_GTK3
+#endif
+
+/**
+ *
+ */
+GMainLoop *loop = NULL;
+
+static gboolean debug = FALSE;
+static gboolean emulate = FALSE;
+static gboolean show_version = FALSE;
+
+// libwnck Version Information is only availible if you have
+// libwnck 3.0 or later
+static gboolean show_wnck_version = FALSE;
+
+static gboolean show_lua_version = FALSE;
+
+static gchar *script_folder = NULL;
+static gchar *temp_folder = NULL;
+
+GFileMonitor *mon = NULL;
+
+gchar *config_filename = NULL;
+
+WnckHandle *my_wnck_handle = NULL;
+
+/**
+ *
+ */
+static void load_list_of_scripts(WnckScreen *screen G_GNUC_UNUSED, WnckWindow *window,
+ GSList *file_list)
+{
+ GSList *temp_file_list = file_list;
+ // set the window to work on
+ set_current_window(window);
+
+ // for every file in the folder - load the script
+ if (event_lists[W_OPEN] != NULL) {
+
+ while(temp_file_list) {
+ gchar *filename = (gchar*)temp_file_list->data;
+
+ // is it a Lua file?
+ if (g_str_has_suffix((gchar*)filename, ".lua")) {
+
+ // init the script, run it
+ if (!run_script(global_lua_state, filename))
+ /**/;
+
+ }
+ temp_file_list=temp_file_list->next;
+ }
+ }
+ return;
+
+}
+
+
+static void window_name_changed_cb(WnckWindow *window)
+{
+ WnckScreen * screen = wnck_window_get_screen(window);
+ if(screen == NULL) return;
+
+ // Handle duplicate name-change events
+ // Simple method: just track the most recent event regardless of window
+ static WnckWindow *previous = NULL;
+ static char *prevname = NULL;
+
+ const char *newname = wnck_window_get_name(window);
+ if (window == previous && prevname && !strcmp (prevname, newname))
+ return;
+ // Store the info for the next event
+ free(prevname);
+ prevname = strdup(newname);
+ previous = window;
+
+ load_list_of_scripts(screen, window, event_lists[W_NAME_CHANGED]);
+}
+
+/**
+ *
+ */
+static void window_opened_cb(WnckScreen *screen, WnckWindow *window)
+{
+ load_list_of_scripts(screen, window, event_lists[W_OPEN]);
+ /*
+ Attach a listener to each window for window-specific changes
+ Safe to do this way as long as the 'user data' parameter is NULL
+ */
+ g_signal_connect(window, "name-changed", (GCallback)window_name_changed_cb, NULL);
+}
+
+
+/**
+ *
+ */
+static void window_closed_cb(WnckScreen *screen, WnckWindow *window)
+{
+ load_list_of_scripts(screen, window, event_lists[W_CLOSE]);
+}
+
+
+/**
+ *
+ */
+static void window_changed_cb(WnckScreen *screen, WnckWindow *window)
+{
+ WnckWindow *cur;
+
+ load_list_of_scripts(screen, window, event_lists[W_BLUR]);
+ cur = wnck_screen_get_active_window(screen);
+ load_list_of_scripts(screen, cur, event_lists[W_FOCUS]);
+}
+
+
+/**
+ *
+ */
+void init_screens()
+{
+ int i;
+ int num_screens;
+
+#ifndef GDK_VERSION_3_10
+ num_screens = gdk_display_get_n_screens(gdk_display_get_default());
+#else
+ num_screens = 1;
+#endif
+
+ for (i=0; i<num_screens; i++) {
+ WnckScreen *screen = wnck_handle_get_screen(my_wnck_handle, i);
+
+ g_signal_connect(screen, "window-opened",
+ (GCallback)window_opened_cb, NULL);
+ g_signal_connect(screen, "window-closed",
+ (GCallback)window_closed_cb, NULL);
+ g_signal_connect(screen, "active-window-changed",
+ (GCallback)window_changed_cb, NULL);
+ }
+}
+
+
+/**
+ * atexit handler - kill the script
+ */
+void devilspie_exit()
+{
+ clear_file_lists();
+ g_free(temp_folder);
+ if (mon)
+ g_object_unref(mon);
+ g_free(config_filename);
+}
+
+
+/**
+ * handle signals that are sent to the application
+ */
+static void signal_handler(int sig)
+{
+ printf("\n%s %d (%s)\n", _("Received signal:"), sig, strsignal(sig));
+
+ done_script_error_messages();
+
+ if (sig == SIGINT) {
+ exit(EXIT_FAILURE);
+ }
+}
+
+
+/**
+ *
+ */
+void print_list(GSList *list)
+{
+ GSList *temp_list;
+ if (list != NULL) {
+ temp_list = list;
+
+ while(temp_list) {
+ gchar *file_name = temp_list->data;
+
+ if (file_name) {
+ if (g_str_has_suffix((gchar*)file_name, ".lua")) {
+ printf("%s\n", (gchar*)file_name);
+ }
+ }
+ temp_list = temp_list->next;
+ }
+ }
+}
+
+
+/**
+ *
+ */
+void print_script_lists()
+{
+ gboolean have_any_files = FALSE;
+ win_event_type i;
+
+ if (debug)
+ printf("------------\n");
+
+ for (i = 0; i < W_NUM_EVENTS; i++) {
+ if (event_lists[i])
+ have_any_files = TRUE;
+ // If we are running debug mode - print the list of files:
+ if (debug) {
+ printf(_("List of Lua files handling \"%s\" events in folder:"),
+ event_names[i]);
+ printf("\n");
+ if (event_lists[i]) {
+ print_list(event_lists[i]);
+ }
+ }
+ }
+
+ if (!have_any_files) {
+ printf("%s\n\n", _("No script files found in the script folder - exiting."));
+ exit(EXIT_SUCCESS);
+ }
+}
+
+
+/**
+ *
+ */
+void folder_changed_callback(GFileMonitor *mon G_GNUC_UNUSED,
+ GFile *first_file,
+ GFile *second_file G_GNUC_UNUSED,
+ GFileMonitorEvent event,
+ gpointer user_data)
+{
+ gchar *our_filename = (gchar*)(user_data);
+
+ // If a file is created or deleted, we need to check the file lists again
+ if ((event == G_FILE_MONITOR_EVENT_CREATED) ||
+ (event == G_FILE_MONITOR_EVENT_DELETED)) {
+
+ clear_file_lists();
+
+ set_current_window(NULL);
+ load_config(our_filename);
+
+ if (debug)
+ printf("Files in folder updated!\n - new lists:\n\n");
+
+ print_script_lists();
+
+ if (debug)
+ printf("-----------\n");
+ }
+
+ // Also monitor if our devilspie2.lua file is changed - since it handles
+ // which files are window close or window open scripts.
+ if (event == G_FILE_MONITOR_EVENT_CHANGED) {
+ if (first_file) {
+ gchar *short_filename = g_file_get_basename(first_file);
+
+ if (g_strcmp0(short_filename, "devilspie2.lua")==0) {
+
+ clear_file_lists();
+
+ set_current_window(NULL);
+ load_config(our_filename);
+
+ print_script_lists();
+
+ if (debug)
+ printf("----------");
+ }
+ }
+ }
+}
+
+
+/**
+ * Program main entry
+ */
+int main(int argc, char *argv[])
+{
+ static const GOptionEntry options[]= {
+ { "debug", 'd', 0, G_OPTION_ARG_NONE, &debug,
+ N_("Print debug info to stdout"), NULL
+ },
+ { "emulate", 'e', 0, G_OPTION_ARG_NONE, &emulate,
+ N_("Don't apply any rules, only emulate execution"), NULL
+ },
+ { "folder", 'f', 0, G_OPTION_ARG_STRING, &script_folder,
+ N_("Search for scripts in this folder"), N_("FOLDER")
+ },
+ { "version", 'v', 0, G_OPTION_ARG_NONE, &show_version,
+ N_("Show Devilspie2 version and quit"), NULL
+ },
+ // libwnck Version Information is only availible if you have
+ // libwnck 3.0 or later
+ { "wnck-version", 'w', 0, G_OPTION_ARG_NONE, &show_wnck_version,
+ N_("Show libwnck version and quit"), NULL
+ },
+ { "lua-version", 'l', 0, G_OPTION_ARG_NONE, &show_lua_version,
+ N_("Show Lua version and quit"), NULL
+ },
+ { NULL }
+ };
+
+ GError *error = NULL;
+ GOptionContext *context;
+
+ // Init gettext stuff
+ setlocale(LC_ALL, "");
+
+ bindtextdomain(PACKAGE, LOCALEDIR);
+ bind_textdomain_codeset(PACKAGE, "");
+ textdomain(PACKAGE);
+
+ gchar *devilspie2_description = g_strdup_printf(_("apply rules on windows"));
+
+ gchar *full_desc_string = g_strdup_printf("- %s", devilspie2_description);
+
+ context = g_option_context_new(full_desc_string);
+ g_option_context_add_main_entries(context, options, NULL);
+ if (!g_option_context_parse(context, &argc, &argv, &error)) {
+ g_print(_("option parsing failed: %s"), error->message);
+ printf("\n");
+ exit(EXIT_FAILURE);
+ }
+
+ gdk_init(&argc, &argv);
+
+ g_free(full_desc_string);
+ g_free(devilspie2_description);
+
+ // if the folder is NULL, default to ~/.config/devilspie2/
+ if (script_folder == NULL) {
+
+ temp_folder = g_build_path(G_DIR_SEPARATOR_S,
+ g_get_user_config_dir(),
+ "devilspie2",
+ NULL);
+
+ // check if the folder does exist
+ if (!g_file_test(temp_folder, G_FILE_TEST_IS_DIR)) {
+
+ // - and if it doesn't, create it.
+ if (g_mkdir(temp_folder, 0700) != 0) {
+ printf("%s\n", _("Couldn't create the default folder for devilspie2 scripts."));
+ exit(EXIT_FAILURE);
+ }
+ }
+
+ script_folder = temp_folder;
+ }
+
+ gboolean shown = FALSE;
+ if (show_version) {
+ printf("Devilspie2 v%s\n", DEVILSPIE2_VERSION);
+ shown = TRUE;
+ }
+ // libwnck Version Information is only availible if you have
+ // libwnck 3.0 or later
+ if (show_wnck_version) {
+#ifdef _DEBUG
+ printf("GTK v%d.%d.%d\n",
+ GTK_MAJOR_VERSION,
+ GTK_MINOR_VERSION,
+ GTK_MICRO_VERSION);
+#endif
+#ifdef HAVE_GTK3
+ printf("libwnck v%d.%d.%d\n",
+ WNCK_MAJOR_VERSION,
+ WNCK_MINOR_VERSION,
+ WNCK_MICRO_VERSION);
+#else
+ printf("libwnck v2.x\n");
+#endif
+ shown = TRUE;
+ }
+ if (show_lua_version) {
+ puts(LUA_VERSION);
+ shown = TRUE;
+ }
+ if (shown)
+ exit(0);
+
+#if (GTK_MAJOR_VERSION >= 3)
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ puts(_("An X11 display is required for devilspie2."));
+ if (getenv("WAYLAND_DISPLAY"))
+ puts(_("Wayland & XWayland are not supported.\nSee https://github.com/dsalt/devilspie2/issues/7"));
+ puts("");
+ return EXIT_FAILURE;
+ }
+#endif
+
+ if (init_script_error_messages()!=0) {
+ printf("%s\n", _("Couldn't init script error messages!"));
+ exit(EXIT_FAILURE);
+ }
+
+ // set the current window to NULL, we don't need to be able to modify
+ // the windows when reading the config
+ set_current_window(NULL);
+
+ config_filename =
+ g_build_filename(script_folder, "devilspie2.lua", NULL);
+
+ if (load_config(config_filename) != 0) {
+
+ devilspie_exit();
+ return EXIT_FAILURE;
+ }
+
+ if (debug) {
+
+ if (emulate) {
+ printf("%s\n\n", _("Running devilspie2 in debug and emulate mode."));
+ } else {
+ printf("%s\n\n", _("Running devilspie2 in debug mode."));
+ }
+
+ printf(_("Using scripts from folder: %s"), script_folder);
+
+ printf("\n");
+
+ devilspie2_debug = TRUE;
+ }
+
+ // Should we only run an emulation (don't modify any windows)
+ if (emulate) devilspie2_emulate = emulate;
+
+ GFile *directory_file;
+ directory_file = g_file_new_for_path(script_folder);
+// mon = g_file_monitor_directory(directory_file, G_FILE_MONITOR_WATCH_MOUNTS,
+ mon = g_file_monitor_directory(directory_file, G_FILE_MONITOR_NONE,
+ NULL, NULL);
+ if (!mon) {
+ printf("%s\n", _("Couldn't create directory monitor!"));
+ return EXIT_FAILURE;
+ }
+
+ g_signal_connect(mon, "changed", G_CALLBACK(folder_changed_callback),
+ (gpointer)(config_filename));
+
+ global_lua_state = init_script();
+ print_script_lists();
+
+ if (debug) printf("------------\n");
+
+ // remove stuff cleanly
+ atexit(devilspie_exit);
+
+ struct sigaction signal_action;
+
+ sigemptyset(&signal_action.sa_mask);
+ signal_action.sa_flags = 0;
+ signal_action.sa_handler = signal_handler;
+
+ if (sigaction(SIGINT, &signal_action, NULL) == -1) {
+ exit(EXIT_FAILURE);
+ }
+
+ my_wnck_handle = wnck_handle_new(WNCK_CLIENT_TYPE_PAGER);
+ init_screens();
+
+ loop=g_main_loop_new(NULL, TRUE);
+ g_main_loop_run(loop);
+
+ return EXIT_SUCCESS;
+}
diff --git a/devilspie/error_strings.c b/devilspie/error_strings.c
new file mode 100644
index 0000000..a04b6af
--- /dev/null
+++ b/devilspie/error_strings.c
@@ -0,0 +1,117 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2012-2017 Andreas Rönnquist
+ * Copyright (C) 2019-2021 Darren Salt
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <glib.h>
+#include <glib/gi18n.h>
+
+#include <stdio.h>
+
+#include "error_strings.h"
+
+const int max_indata_expected;
+gchar *num_indata_expected_errors[] = {NULL, NULL, NULL, NULL, NULL};
+
+gchar *n_or_m_indata_expected_error = NULL;
+gchar *n_to_m_indata_expected_error = NULL;
+
+gchar *at_least_four_indata_expected_error = NULL;
+
+gchar *number_expected_as_indata_error = NULL;
+gchar *boolean_expected_as_indata_error = NULL;
+gchar *string_expected_as_indata_error = NULL;
+
+gchar *number_or_string_expected_as_indata_error = NULL;
+gchar *number_or_string_or_boolean_expected_as_indata_error = NULL;
+
+gchar *integer_greater_than_zero_expected_error = NULL;
+gchar *could_not_find_current_viewport_error = NULL;
+
+gchar *setting_viewport_failed_error = NULL;
+
+gchar *failed_string = NULL;
+
+/**
+ *
+ */
+#define ALLOCATE_ERROR_STRING _("Couldn't allocate error string!")
+#define INIT_ERRMSG(errvar, errtxt) \
+ { \
+ errvar = g_strdup(errtxt); \
+ if (!errvar) { \
+ printf("%s: \"%s\"\n", ALLOCATE_ERROR_STRING, errtxt); \
+ return -1; \
+ } \
+ }
+int init_script_error_messages()
+{
+ INIT_ERRMSG(num_indata_expected_errors[0], _("No parameters expected"));
+ INIT_ERRMSG(num_indata_expected_errors[1], _("One parameter expected"));
+ INIT_ERRMSG(num_indata_expected_errors[2], _("Two parameters expected"));
+ INIT_ERRMSG(num_indata_expected_errors[3], _("Three parameters expected"));
+ INIT_ERRMSG(num_indata_expected_errors[4], _("Four parameters expected"));
+
+ INIT_ERRMSG(n_or_m_indata_expected_error, _("%d or %d parameters expected"));
+ INIT_ERRMSG(n_to_m_indata_expected_error, _("%d to %d parameters expected"));
+
+ INIT_ERRMSG(at_least_four_indata_expected_error, _("At least four parameters expected"));
+
+ INIT_ERRMSG(number_expected_as_indata_error, _("Number expected as parameter"));
+ INIT_ERRMSG(boolean_expected_as_indata_error, _("Boolean expected as parameter"));
+ INIT_ERRMSG(string_expected_as_indata_error, _("String expected as parameter"));
+
+ INIT_ERRMSG(number_or_string_expected_as_indata_error, _("Number or string expected as parameter"));
+ INIT_ERRMSG(number_or_string_or_boolean_expected_as_indata_error, _("Number or string or boolean expected as parameter"));
+
+ INIT_ERRMSG(integer_greater_than_zero_expected_error, _("Integer greater than zero expected"));
+ INIT_ERRMSG(could_not_find_current_viewport_error, _("Could not find current viewport"));
+ INIT_ERRMSG(setting_viewport_failed_error, _("Setting viewport failed"));
+
+ INIT_ERRMSG(failed_string, _("Failed!"));
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+void done_script_error_messages()
+{
+ for (int i = 0; i <= max_indata_expected; i++) {
+ g_free(num_indata_expected_errors[i]);
+ }
+
+ g_free(n_or_m_indata_expected_error);
+ g_free(n_to_m_indata_expected_error);
+
+ g_free(at_least_four_indata_expected_error);
+
+ g_free(number_expected_as_indata_error);
+ g_free(boolean_expected_as_indata_error);
+ g_free(string_expected_as_indata_error);
+
+ g_free(number_or_string_expected_as_indata_error);
+ g_free(number_or_string_or_boolean_expected_as_indata_error);
+
+ g_free(integer_greater_than_zero_expected_error);
+ g_free(could_not_find_current_viewport_error);
+ g_free(setting_viewport_failed_error);
+
+ g_free(failed_string);
+}
diff --git a/devilspie/error_strings.h b/devilspie/error_strings.h
new file mode 100644
index 0000000..278e41b
--- /dev/null
+++ b/devilspie/error_strings.h
@@ -0,0 +1,55 @@
+
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2012-2017 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __HEADER_ERROR_STRINGS_
+#define __HEADER_ERROR_STRINGS_
+
+/**
+ *
+ */
+extern const int max_indata_expected;
+extern gchar *num_indata_expected_errors[];
+
+extern gchar *n_or_m_indata_expected_error;
+extern gchar *n_to_m_indata_expected_error;
+
+extern gchar *at_least_four_indata_expected_error;
+
+extern gchar *number_expected_as_indata_error;
+extern gchar *boolean_expected_as_indata_error;
+extern gchar *string_expected_as_indata_error;
+
+extern gchar *number_or_string_expected_as_indata_error;
+extern gchar *number_or_string_or_boolean_expected_as_indata_error;
+
+extern gchar *integer_greater_than_zero_expected_error;
+extern gchar *could_not_find_current_viewport_error;
+
+extern gchar *setting_viewport_failed_error;
+
+extern gchar *failed_string;
+
+/**
+ *
+ */
+int init_script_error_messages();
+void done_script_error_messages();
+
+
+#endif /*__HEADER_ERROR_STRINGS_*/
diff --git a/devilspie/intl.h b/devilspie/intl.h
new file mode 100644
index 0000000..2e1a809
--- /dev/null
+++ b/devilspie/intl.h
@@ -0,0 +1,28 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2001 Havoc Pennington, 2011-2019 Andreas Rönnquist
+ * Copyright (C) 2019-2021 Darren Salt
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __HEADER_INTL__
+#define __HEADER_INTL__
+
+#include <libintl.h>
+#define _(String) gettext (String)
+#define gettext_noop(String) String
+#define N_(String) gettext_noop (String)
+
+#endif
diff --git a/devilspie/script.c b/devilspie/script.c
new file mode 100644
index 0000000..399680b
--- /dev/null
+++ b/devilspie/script.c
@@ -0,0 +1,352 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2011-2019 Andreas Rönnquist
+ * Copyright (C) 2019-2021 Darren Salt
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+
+
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+
+#include <glib.h>
+
+#include <gdk/gdk.h>
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <locale.h>
+
+#include "compat.h"
+#include "intl.h"
+#include "script.h"
+
+#if (GTK_MAJOR_VERSION >= 3)
+#define HAVE_GTK3
+#endif
+
+#include "script_functions.h"
+
+
+
+/**
+ *
+ */
+gboolean devilspie2_debug = FALSE;
+gboolean devilspie2_emulate = FALSE;
+
+lua_State *global_lua_state = NULL;
+
+/**
+ *
+ */
+lua_State *
+init_script()
+{
+ lua_State *lua = luaL_newstate();
+ luaL_openlibs(lua);
+
+ register_cfunctions(lua);
+
+ return lua;
+}
+
+
+/**
+ *
+ */
+#define DP2_REGISTER(lua, name) lua_register(lua, #name, c_##name)
+void
+register_cfunctions(lua_State *lua)
+{
+ DP2_REGISTER(lua, use_utf8);
+
+ DP2_REGISTER(lua, get_window_name);
+ DP2_REGISTER(lua, get_window_has_name);
+
+ DP2_REGISTER(lua, set_window_position);
+ DP2_REGISTER(lua, set_window_position2);
+ DP2_REGISTER(lua, set_window_size);
+ DP2_REGISTER(lua, set_window_strut);
+
+ DP2_REGISTER(lua, set_window_geometry);
+ DP2_REGISTER(lua, set_window_geometry2);
+
+ DP2_REGISTER(lua, get_application_name);
+
+ DP2_REGISTER(lua, debug_print);
+
+ DP2_REGISTER(lua, shade);
+ DP2_REGISTER(lua, unshade);
+
+ DP2_REGISTER(lua, maximize);
+ lua_register(lua, "maximise", c_maximize);
+ DP2_REGISTER(lua, maximize_horisontally); // deprecated
+ DP2_REGISTER(lua, maximize_horizontally);
+ lua_register(lua, "maximise_horizontally", c_maximize_horizontally);
+ DP2_REGISTER(lua, maximize_vertically);
+ lua_register(lua, "maximise_vertically", c_maximize_vertically);
+ DP2_REGISTER(lua, unmaximize);
+ lua_register(lua, "unmaximise", c_unmaximize);
+
+ DP2_REGISTER(lua, minimize);
+ lua_register(lua, "minimise", c_minimize);
+ DP2_REGISTER(lua, unminimize);
+ lua_register(lua, "unminimise", c_unminimize);
+
+ DP2_REGISTER(lua, decorate_window);
+ DP2_REGISTER(lua, undecorate_window);
+
+ DP2_REGISTER(lua, set_window_workspace);
+ DP2_REGISTER(lua, change_workspace);
+ DP2_REGISTER(lua, get_workspace_count);
+
+ DP2_REGISTER(lua, pin_window);
+ DP2_REGISTER(lua, unpin_window);
+ DP2_REGISTER(lua, stick_window);
+ DP2_REGISTER(lua, unstick_window);
+
+ DP2_REGISTER(lua, close_window);
+
+ DP2_REGISTER(lua, set_adjust_for_decoration);
+
+ DP2_REGISTER(lua, get_window_geometry);
+ DP2_REGISTER(lua, get_window_client_geometry);
+ DP2_REGISTER(lua, get_window_frame_extents);
+
+ DP2_REGISTER(lua, set_skip_tasklist);
+ DP2_REGISTER(lua, set_skip_pager);
+
+ DP2_REGISTER(lua, get_window_is_maximized);
+ lua_register(lua, "get_window_is_maximised", c_get_window_is_maximized);
+
+ DP2_REGISTER(lua, get_window_is_maximized_vertically);
+ lua_register(lua, "get_window_is_maximised_vertically", c_get_window_is_maximized_vertically);
+
+ lua_register(lua, "get_window_is_maximized_horisontally", // deprecated
+ c_get_window_is_maximized_horisontally);
+ DP2_REGISTER(lua, get_window_is_maximized_horizontally);
+ lua_register(lua, "get_window_is_maximised_horizontally",
+ c_get_window_is_maximized_horizontally);
+ DP2_REGISTER(lua, get_window_is_pinned);
+
+ DP2_REGISTER(lua, get_window_is_decorated);
+
+ DP2_REGISTER(lua, set_window_below);
+ DP2_REGISTER(lua, set_window_above);
+ DP2_REGISTER(lua, set_window_fullscreen);
+
+ DP2_REGISTER(lua, make_always_on_top);
+ DP2_REGISTER(lua, set_on_top);
+ DP2_REGISTER(lua, set_on_bottom);
+
+ DP2_REGISTER(lua, get_window_type);
+
+ DP2_REGISTER(lua, get_window_property);
+ DP2_REGISTER(lua, window_property_is_utf8);
+ DP2_REGISTER(lua, get_window_property_full);
+ DP2_REGISTER(lua, get_window_role);
+ DP2_REGISTER(lua, get_window_xid);
+
+ DP2_REGISTER(lua, get_window_class);
+
+ DP2_REGISTER(lua, set_window_property);
+ DP2_REGISTER(lua, delete_window_property);
+
+ DP2_REGISTER(lua, set_viewport);
+
+ DP2_REGISTER(lua, center);
+ lua_register(lua, "centre", c_center);
+
+ DP2_REGISTER(lua, set_window_opacity);
+ lua_register(lua, "set_opacity", c_set_window_opacity);
+
+ DP2_REGISTER(lua, set_window_type);
+
+ DP2_REGISTER(lua, get_screen_geometry);
+
+ DP2_REGISTER(lua, get_window_fullscreen);
+ lua_register(lua, "get_fullscreen", c_get_window_fullscreen);
+
+ DP2_REGISTER(lua, get_window_strut);
+
+ // wnck_window_get_class_{instance,group}_name are only availible on wnck 3 and later
+ DP2_REGISTER(lua, get_class_instance_name);
+ DP2_REGISTER(lua, get_class_group_name);
+
+ DP2_REGISTER(lua, focus);
+ lua_register(lua, "focus_window", c_focus);
+
+ DP2_REGISTER(lua, get_monitor_index);
+ DP2_REGISTER(lua, get_monitor_geometry);
+
+ DP2_REGISTER(lua, xy);
+ DP2_REGISTER(lua, xywh);
+
+ DP2_REGISTER(lua, on_geometry_changed);
+
+ DP2_REGISTER(lua, get_process_name);
+
+ DP2_REGISTER(lua, millisleep);
+}
+
+
+/**
+ *
+ */
+static ATTR_MALLOC gchar *error_add_backtrace(lua_State *lua, const char *msg)
+{
+ int r, level = 0, lines = 0;
+ lua_Debug state;
+ const gchar *header = _("Backtrace:\n");
+ gchar *backtrace = (gchar*)msg;
+
+ while ((r = lua_getstack(lua, level, &state)) != 0)
+ {
+ lua_getinfo(lua, "Sln", &state);
+ // only report script locations;
+ // C code has name="[C]" & currentline=-1
+ if (state.currentline > 0) {
+ char *traced = state.name
+ ? g_strdup_printf("%s\n%s %s:%d [%-6s] %s", backtrace, header, state.short_src, state.currentline, state.namewhat, state.name)
+ : g_strdup_printf("%s\n%s %s:%d [%-6s]", backtrace, header, state.short_src, state.currentline, state.what);
+ header = "";
+ if (backtrace != msg)
+ g_free(backtrace);
+ backtrace = traced;
+ ++lines;
+ }
+ ++level;
+ }
+
+ if (lines > 1)
+ return backtrace;
+
+ if (backtrace != msg)
+ g_free(backtrace);
+ return g_strdup(msg);
+}
+
+static ATTR_MALLOC gchar *error_add_location(lua_State* lua, const char *msg)
+{
+ lua_Debug state;
+
+ int r = lua_getstack(lua, 0, &state);
+ if (r == 0)
+ return g_strdup(msg);
+ lua_getinfo(lua, "Sln", &state);
+ // the error handler will add a backtrace, so no need for function info
+ return g_strdup_printf("%s:%d: %s", state.short_src, state.currentline, msg);
+}
+
+static gboolean timedout = FALSE;
+
+static void timeout_script(int sig)
+{
+ timedout = TRUE;
+}
+
+static void check_timeout_script(lua_State *lua, lua_Debug *state)
+{
+ // state is invalid?
+ if (!timedout)
+ return;
+ // don't add backtrace etc. here; just the location
+ gchar *msg = error_add_location(lua, _("script timed out"));
+ lua_pushstring(lua, msg);
+ g_free(msg);
+ lua_error(lua);
+}
+
+static int script_error(lua_State *lua)
+{
+ const char *msg = lua_tostring(lua, -1);
+ lua_pop(lua, 1);
+ // only the backtrace here, as the location's probably present already
+ gchar *fullmsg = error_add_backtrace(lua, msg);
+ lua_pushstring(lua, fullmsg);
+ g_free(fullmsg);
+ return 1;
+}
+
+
+/**
+ *
+ */
+int
+run_script(lua_State *lua, const char *filename)
+{
+#define SCRIPT_TIMEOUT_SECONDS 5
+
+ if (!lua)
+ return -1;
+
+ lua_pushcfunction(lua, script_error);
+ int errpos = lua_gettop(lua);
+
+ int result = luaL_loadfile(lua, filename);
+
+ if (result) {
+ // We got an error, print it
+ printf(_("Error: %s\n"), lua_tostring(lua, -1));
+ lua_remove(lua, errpos); // unstack the error handler
+ lua_pop(lua, 1);
+ return -1;
+ }
+
+ // Okay, loaded the script; now run it
+
+ struct sigaction newact, oldact;
+ newact.sa_handler = timeout_script;
+ sigemptyset(&newact.sa_mask);
+ newact.sa_flags = 0;
+
+ timedout = FALSE;
+ lua_sethook(lua, check_timeout_script, LUA_MASKCOUNT, 1);
+ sigaction(SIGALRM, &newact, &oldact);
+ alarm(SCRIPT_TIMEOUT_SECONDS);
+
+ int s = lua_pcall(lua, 0, LUA_MULTRET, errpos);
+
+ alarm(0);
+ sigaction(SIGALRM, &oldact, NULL);
+
+ lua_remove(lua, errpos); // unstack the error handler
+
+ if (s) {
+ // no info to add here; just output the error
+ printf(_("Error: %s\n"), lua_tostring(lua, -1));
+ lua_pop(lua, 1); // else we leak it
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+void
+done_script(lua_State *lua)
+{
+ if (lua)
+ lua_close(lua);
+
+ //lua=NULL;
+}
+
diff --git a/devilspie/script.h b/devilspie/script.h
new file mode 100644
index 0000000..471c8cb
--- /dev/null
+++ b/devilspie/script.h
@@ -0,0 +1,42 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2011-2019 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#ifndef __HEADER_SCRIPT_
+#define __HEADER_SCRIPT_
+
+/**
+ *
+ */
+
+
+/**
+ *
+ */
+lua_State *init_script();
+
+void register_cfunctions(lua_State *lua);
+int run_script(lua_State *lua, const char *filename);
+void done_script(lua_State *lua);
+
+
+extern gboolean devilspie2_debug;
+extern gboolean devilspie2_emulate;
+
+extern lua_State *global_lua_state;
+
+#endif /*__HEADER_SCRIPT_*/
diff --git a/devilspie/script_functions.c b/devilspie/script_functions.c
new file mode 100644
index 0000000..240d582
--- /dev/null
+++ b/devilspie/script_functions.c
@@ -0,0 +1,2659 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2011-2019 Andreas Rönnquist
+ * Copyright (C) 2019-2021 Darren Salt
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#include "glib.h"
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <errno.h>
+
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+
+#include <X11/Xlib.h>
+#include <X11/Xatom.h>
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <locale.h>
+
+#include <limits.h>
+
+#include "intl.h"
+
+#include <lua.h>
+#include <lualib.h>
+#include <lauxlib.h>
+
+#include <X11/extensions/Xrandr.h>
+
+#if (GTK_MAJOR_VERSION >= 3)
+#define HAVE_GTK3
+#endif
+
+
+#include "compat.h"
+
+#include "script_functions.h"
+
+#include "script.h"
+
+#include "xutils.h"
+
+#include "error_strings.h"
+
+#define DEPRECATED() fprintf(stderr, "warning: deprecated function %s called\n", __func__ + 2);
+
+/**
+ *
+ */
+WnckWindow *current_window = NULL;
+
+static Bool current_time_cb(Display *display, XEvent *xevent, XPointer arg)
+{
+ Window wnd = GPOINTER_TO_UINT(arg);
+
+ if (xevent->type == PropertyNotify &&
+ xevent->xproperty.window == wnd &&
+ xevent->xproperty.atom == my_wnck_atom_get("WM_NAME"))
+ return True;
+
+ return False;
+}
+
+/**
+ * Get current X11 timestamp.
+ *
+ * Unfortunately, gtk_get_current_event_time() does not work here
+ * because we cannot assume we are inside an event.
+ *
+ * Getting this timestamp is tricky. According to a comment in the ICCCM
+ * specification (https://tronche.com/gui/x/icccm/sec-2.html#s-2.1):
+ *
+ * A zero-length append to a property is a way to obtain a
+ * timestamp for this purpose; the timestamp is in the
+ * corresponding PropertyNotify event.
+ *
+ * So here we are zero-length appending to the "WM_NAME" property.
+ */
+static guint32 current_time(void)
+{
+ WnckWindow *window;
+ gulong wnd;
+ Display *dpy;
+ Atom prop;
+ XEvent xevent;
+
+ window = get_current_window();
+ if (!window)
+ return GDK_CURRENT_TIME;
+
+ dpy = gdk_x11_get_default_xdisplay();
+ wnd = wnck_window_get_xid(window);
+ prop = my_wnck_atom_get("WM_NAME");
+ XChangeProperty(dpy, wnd, prop, XA_STRING, 8, PropModeAppend, NULL, 0);
+
+ /* Wait for the event to succeed */
+ XIfEvent(dpy, &xevent, current_time_cb, GUINT_TO_POINTER(wnd));
+ return xevent.xproperty.time;
+}
+
+
+/**
+ * Check for the correct parameter count.
+ * Failure will log a lua error and return False.
+ * Success returns True.
+ */
+static Bool check_param_count(lua_State *lua, const char *funcname, int expected)
+{
+ int top = lua_gettop(lua);
+ gchar* error_message;
+
+ if (top != expected) {
+ error_message = num_indata_expected_errors[expected];
+ if (error_message == NULL) {
+ error_message = failed_string;
+ }
+ luaL_error(lua, "%s: %s", funcname, error_message);
+ return False;
+ }
+ return True;
+}
+
+/**
+ * Check for the correct parameter count, one of two expected values.
+ * Failure will log a lua error and return False.
+ * Success returns True.
+ */
+static Bool check_param_counts(lua_State *lua, const char *funcname, int expected1, int expected2)
+{
+ int top = lua_gettop(lua);
+ gchar* error_message;
+
+ if (top != expected1 && top != expected2) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ error_message = g_strdup_printf(n_or_m_indata_expected_error, expected1, expected2);
+#pragma GCC diagnostic pop
+ luaL_error(lua, "%s: %s", funcname, error_message);
+ g_free(error_message);
+ return False;
+ }
+ return True;
+}
+
+/**
+ * Check for the correct parameter count in a range of expected values.
+ * Failure will log a lua error and return False.
+ * Success returns True.
+ */
+static Bool check_param_counts_range(lua_State *lua, char *funcname, int expected_min, int expected_max)
+{
+ int top = lua_gettop(lua);
+ gchar* error_message;
+
+ if (top < expected_min || top > expected_max) {
+#pragma GCC diagnostic push
+#pragma GCC diagnostic ignored "-Wformat-nonliteral"
+ error_message = g_strdup_printf(n_to_m_indata_expected_error, expected_min, expected_max);
+#pragma GCC diagnostic pop
+ luaL_error(lua, "%s: %s", funcname, error_message);
+ g_free(error_message);
+ return False;
+ }
+ return True;
+}
+
+static gboolean default_use_utf8 = False;
+
+gboolean c_use_utf8(lua_State *lua)
+{
+ if (!check_param_counts(lua, "use_utf8", 0, 1)) {
+ return 0;
+ }
+
+ gboolean v = default_use_utf8;
+ int top = lua_gettop(lua);
+
+ if (top) {
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "use_utf8: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ int value = lua_toboolean(lua, 1);
+ v = (gboolean)(value);
+ }
+
+ lua_pushboolean(lua, default_use_utf8);
+ default_use_utf8 = v;
+ return 1;
+}
+
+static gboolean adjusting_for_decoration = FALSE;
+
+int c_set_adjust_for_decoration(lua_State *lua)
+{
+ if (!check_param_counts(lua, "set_adjust_for_decoration", 0, 1)) {
+ return 0;
+ }
+
+ gboolean v = TRUE;
+
+ if (lua_gettop(lua)) {
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_adjust_for_decoration: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ int value = lua_toboolean(lua, 1);
+ v = (gboolean)(value);
+ }
+
+ adjusting_for_decoration = v;
+ return 0;
+}
+
+
+/**
+ * returns the window name
+ */
+int c_get_window_name(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_name", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ const char *test = window ? wnck_window_get_name(window) : "";
+
+ lua_pushstring(lua, test);
+
+ // one item returned (the window name as a string)
+ return 1;
+}
+
+
+/**
+ * c_get_window_name always returns a string, even if a window hasn't
+ * got a name - use this function to determine if a window really has
+ * a name or not.
+ * returns a boolean true or false
+ */
+int c_get_window_has_name(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_has_name", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ gboolean has_name = window ? wnck_window_has_name(window) : FALSE;
+
+ lua_pushboolean(lua, has_name);
+
+ return 1;
+}
+
+
+/**
+ * Internal code for calculating the new position of the window
+ * Returns -1 on parameter error, +1 (TRUE) on success (or if emulating), else 0 (FALSE)
+ */
+static int do_set_window_position_internal(lua_State *restrict lua, const char *const fn, int *restrict px, int *restrict py, gboolean with_size)
+{
+ int monitor = with_size ? 5 : 3;
+ if (!check_param_counts(lua, fn, monitor - 1, monitor)) {
+ return -1;
+ }
+
+ int top = lua_gettop(lua);
+
+ for (int i = 1; i <= top; ++i)
+ if (lua_type(lua, i) != LUA_TNUMBER) {
+ luaL_error(lua, "%s: %s", fn, number_expected_as_indata_error);
+ return -1;
+ }
+
+ *px = lua_tonumber(lua, 1);
+ *py = lua_tonumber(lua, 2);
+
+ if (top == monitor) {
+ monitor = lua_tonumber (lua, monitor) - 1;
+ if (monitor < MONITOR_ALL || monitor >= get_monitor_count())
+ monitor = 0; // FIXME: primary monitor; show warning?
+ } else
+ monitor = MONITOR_NONE;
+
+ if (devilspie2_emulate)
+ return TRUE;
+
+ WnckWindow *window = get_current_window();
+ if (!window)
+ return FALSE;
+
+ if (monitor != MONITOR_NONE) {
+ /* +ve x: relative to left
+ * -ve x: relative to right (bitwise NOT)
+ * +ve y: relative to top
+ * -ve y: relative to bottom (bitwise NOT)
+ */
+ GdkRectangle bounds, geom;
+ wnck_window_get_geometry(window, &geom.x, &geom.y, &geom.width, &geom.height);
+ monitor = get_monitor_or_workspace_geometry(monitor, window, &bounds);
+ if (monitor == MONITOR_NONE)
+ return FALSE;
+
+ if (*px < 0)
+ *px = bounds.x + bounds.width - ~*px - geom.width;
+ else
+ *px += bounds.x;
+ if (*py < 0)
+ *py = bounds.y + bounds.height - ~*py - geom.height;
+ else
+ *py += bounds.y;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ * Set the Window Geometry
+ * set_window_geometry(x,y,xsize,ysize,[monitor_index]);
+ */
+int c_set_window_geometry(lua_State *lua)
+{
+ int x, y;
+ int ret = do_set_window_position_internal(lua, "set_window_geometry", &x, &y, TRUE);
+
+ if (ret < 0)
+ return 0;
+ else if (ret > 0) {
+ int xsize = lua_tonumber(lua, 3);
+ int ysize = lua_tonumber(lua, 4);
+ WnckWindow *window = get_current_window();
+ if (window) {
+ set_window_geometry(window, x, y, xsize, ysize, adjusting_for_decoration);
+ }
+ }
+
+ lua_pushboolean(lua, ret);
+ return 1;
+}
+
+
+/**
+ * Set the Window Geometry2
+ * set_window_geometry2(x,y,xsize,ysize,[monitor_index]);
+ */
+int c_set_window_geometry2(lua_State *lua)
+{
+ int x, y;
+ int ret = do_set_window_position_internal(lua, "set_window_geometry2", &x, &y, TRUE);
+
+ if (ret < 0)
+ return 0;
+ else if (ret > 0) {
+ int xsize = lua_tonumber(lua, 3);
+ int ysize = lua_tonumber(lua, 4);
+ WnckWindow *window = get_current_window();
+ if (window) {
+ XMoveResizeWindow(gdk_x11_get_default_xdisplay(),
+ wnck_window_get_xid(window),
+ x, y,
+ xsize, ysize);
+ }
+ }
+
+ lua_pushboolean(lua, ret);
+ return 1;
+}
+
+
+/**
+ * Set the position of the window
+ */
+int c_set_window_position(lua_State *lua)
+{
+ int x, y;
+ int ret = do_set_window_position_internal(lua, "set_window_position", &x, &y, FALSE);
+
+ if (ret < 0)
+ return 0;
+ else if (ret > 0) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ if (adjusting_for_decoration)
+ adjust_for_decoration(window, &x, &y, NULL, NULL);
+ wnck_window_set_geometry(window,
+ WNCK_WINDOW_GRAVITY_CURRENT,
+ WNCK_WINDOW_CHANGE_X + WNCK_WINDOW_CHANGE_Y,
+ x, y, -1, -1);
+ }
+ }
+
+ lua_pushboolean(lua, ret);
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_set_window_position2(lua_State *lua)
+{
+ int x, y;
+ int ret = do_set_window_position_internal(lua, "set_window_position2", &x, &y, FALSE);
+
+ if (ret < 0)
+ return 0;
+ else if (ret > 0) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ XMoveWindow(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()),
+ wnck_window_get_xid(window),
+ x, y);
+ }
+ }
+
+ lua_pushboolean(lua, ret);
+ return 1;
+}
+
+
+/**
+ * Sets the size of the window
+ */
+int c_set_window_size(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_window_size", 2)) {
+ return 0;
+ }
+
+ int type1 = lua_type(lua, 1);
+ int type2 = lua_type(lua, 2);
+
+ if ((type1 != LUA_TNUMBER) || (type2 != LUA_TNUMBER)) {
+ luaL_error(lua,"set_window_size: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ int x = lua_tonumber(lua,1);
+ int y = lua_tonumber(lua,2);
+
+ if (!devilspie2_emulate) {
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+
+ devilspie2_error_trap_push();
+ if (adjusting_for_decoration)
+ adjust_for_decoration (window, NULL, NULL, &x, &y);
+ wnck_window_set_geometry(window,
+ WNCK_WINDOW_GRAVITY_CURRENT,
+ WNCK_WINDOW_CHANGE_WIDTH + WNCK_WINDOW_CHANGE_HEIGHT,
+ -1, -1, x, y);
+
+ if (devilspie2_error_trap_pop()) {
+ gchar *temperror=
+ g_strdup_printf("set_window_size: %s", failed_string);
+ g_printerr("%s", temperror);
+
+ g_free(temperror);
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+#define NUM_STRUTS 12
+static gulong *get_default_struts(Display *dpy)
+{
+ int screen = DefaultScreen(dpy);
+ int width, height;
+
+ static gulong struts[NUM_STRUTS];
+ memset (struts, 0, sizeof(struts));
+#ifdef HAVE_XRANDR
+ // If we have xrandr (we probably do), get the maximum screen size
+ int x; // throwaway
+ XRRGetScreenSizeRange (dpy, RootWindow(dpy, screen),
+ &x, &x, &width, &height);
+#else
+ // Otherwise, fall back to the current size
+ width = DisplayWidth(dpy, screen);
+ height = DisplayHeight(dpy, screen);
+#endif
+ struts[5] = struts[7] = height;
+ struts[9] = struts[11] = width;
+
+ return struts;
+}
+
+/**
+ * Sets the window strut
+ */
+int c_set_window_strut(lua_State *lua)
+{
+ int top = lua_gettop(lua);
+
+ if (top < 4) {
+ luaL_error(lua,"set_window_strut: %s", at_least_four_indata_expected_error);
+ return 0;
+ }
+
+ if (top > NUM_STRUTS)
+ top = NUM_STRUTS;
+
+ if (!devilspie2_emulate) {
+ Display *dpy = gdk_x11_get_default_xdisplay();
+
+ gulong *struts = get_default_struts(dpy);
+ for (int i = 0; i < top; i++) {
+ struts[i] = lua_tonumber(lua, i + 1);
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ XChangeProperty(dpy,
+ wnck_window_get_xid(window),
+ XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False), XA_CARDINAL,
+ 32,
+ PropModeReplace,
+ (unsigned char*)struts,
+ NUM_STRUTS);
+ XSync(dpy, False);
+ }
+ }
+
+ return 0;
+}
+
+/**
+ * Gets the window strut
+ */
+int c_get_window_strut(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_strut", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (!window)
+ return 0;
+
+ Display *dpy = gdk_x11_get_default_xdisplay();
+ gulong *struts = NULL;
+ int len = 0;
+
+ gboolean ret = my_wnck_get_cardinal_list (wnck_window_get_xid(window),
+ XInternAtom(dpy, "_NET_WM_STRUT_PARTIAL", False),
+ &struts, &len);
+ /* if that fails, try reading the older, deprecated property */
+ if (!ret)
+ ret = my_wnck_get_cardinal_list (wnck_window_get_xid(window),
+ XInternAtom(dpy, "_NET_WM_STRUT", False),
+ &struts, &len);
+
+ if (len) {
+ int i;
+ if (len > NUM_STRUTS)
+ len = NUM_STRUTS;
+
+ lua_createtable(lua, NUM_STRUTS, 0);
+ for (i = 0; i < len; ++i) {
+ lua_pushinteger(lua, struts[i]);
+ lua_rawseti(lua, -2, i + 1);
+ }
+ g_free(struts);
+
+ // pad out with default values if necessary
+ if (len < NUM_STRUTS) {
+ struts = get_default_struts(dpy);
+ for (; i < NUM_STRUTS; ++i) {
+ lua_pushinteger(lua, struts[i]);
+ lua_rawseti(lua, -2, i + 1);
+ }
+ }
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * Sets the window on top of all others and locks it "always on top"
+ */
+int c_make_always_on_top(lua_State *lua)
+{
+ if (!check_param_count(lua, "make_always_on_top", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ wnck_window_make_above(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * sets the window on top of all the others
+ */
+int c_set_on_top(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_on_top", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window)
+ XRaiseWindow(gdk_x11_get_default_xdisplay(), wnck_window_get_xid(window));
+ }
+
+ return 0;
+}
+
+
+/**
+ * sets the window below all the others
+ */
+int c_set_on_bottom(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_on_bottom", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window)
+ XLowerWindow(gdk_x11_get_default_xdisplay(), wnck_window_get_xid(window));
+ }
+
+ return 0;
+}
+
+
+/**
+ * returns the application name
+ */
+int c_get_application_name(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_application_name", 0)) {
+ return 0;
+ }
+
+ const char *application_name = "";
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ WnckApplication *application=
+ wnck_window_get_application(get_current_window());
+ if (application)
+ application_name = wnck_application_get_name(application);
+ }
+
+ // one item returned - the application name as a string.
+ lua_pushstring(lua, application_name);
+
+ return 1;
+}
+
+
+/**
+ * lua_Bprint from http://www.lua.org/source/5.1/lbaselib.c.html
+ * but with the change that fputs is only called if devilspie2_debug is
+ * set to TRUE
+ */
+int c_debug_print(lua_State *lua)
+{
+ int n = lua_gettop(lua); /* number of arguments */
+ lua_getglobal(lua, "tostring");
+
+ for (int i = 1; i <= n; i++) {
+ lua_pushvalue(lua, -1); /* function to be called */
+ lua_pushvalue(lua, i); /* value to print */
+ lua_call(lua, 1, 1);
+
+ const char *s = lua_tostring(lua, -1); /* get result */
+ if (s == NULL)
+ return luaL_error(lua, "'tostring' must return a string to 'print'");
+ if (i > 1) {
+ if (devilspie2_debug) fputs("\t", stdout);
+ }
+ if (devilspie2_debug) fputs(s, stdout);
+ lua_pop(lua, 1); /* pop result */
+ }
+ if (devilspie2_debug) {
+ fputs("\n", stdout);
+ fflush(stdout);
+ }
+
+ return 0;
+}
+
+
+/**
+ * "Shades" the window
+ */
+int c_shade(lua_State *lua)
+{
+ if (!check_param_count(lua, "shade", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ wnck_window_shade(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * "Unshades" the window
+ */
+int c_unshade(lua_State *lua)
+{
+ if (!check_param_count(lua, "unshade", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ wnck_window_unshade(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Minimizes the window
+ */
+int c_minimize(lua_State *lua)
+{
+ if (!check_param_count(lua, "minimize", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ wnck_window_minimize(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * unminimizes window
+ */
+int c_unminimize(lua_State *lua)
+{
+ if (!check_param_count(lua, "unminimize", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ wnck_window_unminimize (window, current_time());
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * sets the window that the scripts are affecting
+ */
+void set_current_window(WnckWindow *window)
+{
+ current_window=window;
+}
+
+
+/**
+ * gets the window that the scripts are affecting
+ */
+WnckWindow *get_current_window()
+{
+ return current_window;
+}
+
+
+/**
+ * Decorates a window
+ */
+int c_undecorate_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "undecorate_window", 0)) {
+ return 0;
+ }
+
+ gboolean result = TRUE;
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ gulong xid = wnck_window_get_xid(window);
+
+ if (!undecorate_window(xid)) {
+ result=FALSE;
+ }
+ }
+ }
+
+ lua_pushboolean(lua,result);
+
+ return 1;
+}
+
+
+/**
+ * undecorates a window
+ */
+int c_decorate_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "decorate_window", 0)) {
+ return 0;
+ }
+
+ gboolean result = TRUE;
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ gulong xid = wnck_window_get_xid(window);
+
+ if (!decorate_window(xid)) {
+ result=FALSE;
+ }
+ }
+ }
+
+ lua_pushboolean(lua,result);
+
+ return 1;
+}
+
+
+
+/**
+ * Checks if a window is decorated
+ */
+int c_get_window_is_decorated(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_is_decorated", 0)) {
+ return 0;
+ }
+
+ gboolean result = TRUE;
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ result = get_decorated(wnck_window_get_xid(window));
+ }
+ }
+
+ lua_pushboolean(lua,result);
+
+ return 1;
+}
+
+/**
+ Given a workspace name, perform a linear, case-sensitive search for
+ a workspace with with said name.
+
+ Returns the first found or -1
+ */
+int find_workspace_with_name(gchar *in_workspace_name, WnckWindow *window){
+ if(window == NULL) {
+ window = get_current_window();
+ }
+ if(window == NULL || in_workspace_name == NULL || strlen(in_workspace_name) <= 0) {
+ return -1;
+ }
+
+ WnckScreen *screen = wnck_window_get_screen(window);
+
+ for (int space = 0; space < wnck_screen_get_workspace_count(screen); space++) {
+ WnckWorkspace *workspace = wnck_screen_get_workspace(screen, space);
+ if(workspace == NULL) //Theoretically possible
+ continue;
+ if(0 == g_strcmp0(in_workspace_name, wnck_workspace_get_name(workspace))){
+ return space;
+ }
+ }
+
+ return -1;
+}
+
+/**
+ * Move a window to a specific workspace
+ */
+int c_set_window_workspace(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_window_workspace", 1)) {
+ return 0;
+ }
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TNUMBER && type != LUA_TSTRING ) {
+ luaL_error(lua, "set_window_workspace: %s",
+ number_or_string_expected_as_indata_error);
+ return 0;
+ }
+
+ int workspace_idx0 = -1;
+ gchar *workspace_name = NULL;
+
+ switch (type) {
+ case LUA_TNUMBER:
+ workspace_idx0 = lua_tonumber(lua, 1) - 1;
+ break;
+ case LUA_TSTRING:
+ workspace_name = (gchar*)lua_tostring(lua, 1);
+ workspace_idx0 = find_workspace_with_name(workspace_name, get_current_window());
+ if(workspace_idx0 == -1) {
+ g_warning(_("A workspace with the name '%s' does not exist!"), workspace_name);
+ }
+ break;
+ default: break;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (window && workspace_idx0 > -1) {
+ WnckScreen *screen = wnck_window_get_screen(window);
+ WnckWorkspace *workspace = wnck_screen_get_workspace(screen, workspace_idx0);
+
+ if (!workspace) {
+ g_warning(_("Workspace number %d does not exist!"), workspace_idx0+1);
+ }
+ if (!devilspie2_emulate) {
+ wnck_window_move_to_workspace(window, workspace);
+ }
+ }
+
+ lua_pushboolean(lua,TRUE);
+
+ return 1;
+}
+
+
+/**
+ * Makes a workspace the active one
+ */
+int c_change_workspace(lua_State *lua)
+{
+ if (!check_param_count(lua, "change_workspace", 1)) {
+ return 0;
+ }
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TNUMBER && type != LUA_TSTRING) {
+ luaL_error(lua,"change_workspace: %s", number_or_string_expected_as_indata_error);
+ return 0;
+ }
+
+ int workspace_idx0 = -1;
+ gchar *workspace_name = NULL;
+
+ switch (type) {
+ case LUA_TNUMBER:
+ workspace_idx0 = lua_tonumber(lua, 1) - 1;
+ break;
+ case LUA_TSTRING:
+ workspace_name = (gchar*)lua_tostring(lua, 1);
+ workspace_idx0 = find_workspace_with_name(workspace_name, get_current_window());
+ if(workspace_idx0 == -1) {
+ g_warning(_("A workspace with the name '%s' does not exist!"), workspace_name);
+ }
+ break;
+ default: break;
+ }
+
+ WnckWindow *window = get_current_window();
+ if (window && workspace_idx0 > -1) {
+ WnckScreen *screen = wnck_window_get_screen(window);
+ WnckWorkspace *workspace = wnck_screen_get_workspace(screen, workspace_idx0);
+
+ if (!workspace) {
+ g_warning(_("Workspace number %d does not exist!"), workspace_idx0+1);
+ }
+
+ gint64 timestamp = g_get_real_time();
+ if (!devilspie2_emulate) {
+ wnck_workspace_activate(workspace, timestamp / 1000000);
+ }
+ }
+
+ lua_pushboolean(lua, TRUE);
+
+ return 1;
+}
+
+
+/**
+ * Return workspace count
+ */
+int c_get_workspace_count(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_workspace_count", 0)) {
+ return 0;
+ }
+
+ int count = 0;
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ WnckScreen *screen = wnck_window_get_screen(window);
+ count = wnck_screen_get_workspace_count(screen);
+ }
+
+ lua_pushinteger(lua, count);
+
+ return 1;
+}
+
+
+/**
+ * Unmaximize window
+ */
+int c_unmaximize(lua_State *lua)
+{
+ if (!check_param_count(lua, "unmaximize", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_unmaximize(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Maximize Window
+ */
+int c_maximize(lua_State *lua)
+{
+ if (!check_param_count(lua, "maximize", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_maximize(window);
+ }
+ }
+ return 0;
+}
+
+
+/**
+ * Maximize Window Vertically
+ */
+int c_maximize_vertically(lua_State *lua)
+{
+ if (!check_param_count(lua, "maximize_vertically", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_maximize_vertically(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Maximize the window horizontally
+ */
+int c_maximize_horizontally(lua_State *lua)
+{
+ if (!check_param_count(lua, "maximize_horizontally", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_maximize_horizontally(window);
+ }
+ }
+
+ return 0;
+}
+
+int c_maximize_horisontally(lua_State *lua)
+{
+ DEPRECATED();
+ return c_maximize_horizontally(lua);
+}
+
+
+/**
+ * Pins the window
+ */
+int c_pin_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "pin_window", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_pin(window);
+ }
+ }
+
+ return 0;
+}
+
+
+
+/**
+ * Unpin the window
+ */
+int c_unpin_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "unpin_window", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_unpin(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Sticks the window
+ */
+int c_stick_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "stick_window", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_stick(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * Unstick the window
+ */
+int c_unstick_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "unstick_window", 0)) {
+ return 0;
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_unstick(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ * return the geometry of the current window to the Lua script
+ */
+int c_get_window_geometry(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_geometry", 0)) {
+ return 0;
+ }
+
+ int x = 0, y = 0, width = 0, height = 0;
+
+ WnckWindow *window = get_current_window();
+ if (window)
+ {
+ wnck_window_get_geometry(window, &x, &y, &width, &height);
+ }
+
+ lua_pushinteger(lua, x);
+ lua_pushinteger(lua, y);
+ lua_pushinteger(lua, width);
+ lua_pushinteger(lua, height);
+
+ return 4;
+}
+
+
+/**
+ * return the client geometry of the current window to the Lua script
+ * this is excluding the window manager frame
+ */
+int c_get_window_client_geometry(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_client_geometry", 0)) {
+ return 0;
+ }
+
+ int x = 0, y = 0, width = 0, height = 0;
+
+ WnckWindow *window = get_current_window();
+ if (window)
+ {
+ wnck_window_get_client_window_geometry(window, &x, &y, &width, &height);
+ }
+
+ lua_pushinteger(lua, x);
+ lua_pushinteger(lua, y);
+ lua_pushinteger(lua, width);
+ lua_pushinteger(lua, height);
+
+ return 4;
+}
+
+
+/**
+ * return the window frame extents
+ */
+int c_get_window_frame_extents(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_frame_extents", 0)) {
+ return 0;
+ }
+
+ int left = 0, right = 0, top = 0, bottom = 0;
+
+ WnckWindow *window = get_current_window();
+ if (window) {
+ // Order of preference:
+ // _NET_FRAME_EXTENTS
+ // Calculation from geometries
+
+ Display *dpy = gdk_x11_get_default_xdisplay();
+ gulong *extents = 0;
+ int len = 0;
+
+ my_wnck_get_cardinal_list (wnck_window_get_xid(window),
+ XInternAtom(dpy, "_NET_FRAME_EXTENTS", False),
+ &extents, &len);
+ if (len >= 4) {
+ // _NET_FRAME_EXTENTS
+ left = extents[0];
+ right = extents[1];
+ top = extents[2];
+ bottom = extents[3];
+ g_free(extents);
+ }
+ else {
+ // Calculation from geometries
+ int frame[4] = {}, client[4] = {};
+
+ wnck_window_get_geometry(window, frame, frame + 1, frame + 2, frame + 3);
+ wnck_window_get_client_window_geometry(window, client, client + 1, client + 2, client + 3);
+ left = client[0] - frame[0];
+ right = frame[2] - client[2] - left;
+ top = client[1] - frame[1];
+ bottom = frame[3] - client[3] - top;
+ }
+ }
+
+ lua_pushinteger(lua, left);
+ lua_pushinteger(lua, right);
+ lua_pushinteger(lua, top);
+ lua_pushinteger(lua, bottom);
+
+ return 4;
+}
+
+
+/**
+ *
+ */
+int c_set_skip_tasklist(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_skip_tasklist", 1)) {
+ return 0;
+ }
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_skip_tasklist: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ int value = lua_toboolean(lua, 1);
+
+ gboolean skip_tasklist = (gboolean)(value);
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_set_skip_tasklist(window, skip_tasklist);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_set_skip_pager(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_skip_pager", 1)) {
+ return 0;
+ }
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_skip_pager: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ int value = lua_toboolean(lua, 1);
+
+ gboolean skip_pager = (gboolean)(value);
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ wnck_window_set_skip_pager(window, skip_pager);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_get_window_is_maximized(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_is_maximized", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ gboolean is_maximized = window ? wnck_window_is_maximized(window) : FALSE;
+
+ lua_pushboolean(lua, is_maximized);
+
+ return 1;
+}
+
+/**
+ *
+ */
+int c_get_window_is_maximized_vertically(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_is_maximized_vertically", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ gboolean is_vertically_maximized = window ? wnck_window_is_maximized_vertically(window) : FALSE;
+
+ lua_pushboolean(lua, is_vertically_maximized);
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_get_window_is_maximized_horizontally(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_is_maximized_horizontally", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ gboolean is_horizontally_maximized = window ? wnck_window_is_maximized_horizontally(window) : FALSE;
+
+ lua_pushboolean(lua, is_horizontally_maximized);
+
+ return 1;
+}
+
+int c_get_window_is_maximized_horisontally(lua_State *lua)
+{
+ DEPRECATED();
+ return c_get_window_is_maximized_horizontally(lua);
+}
+
+
+/**
+ *
+ */
+int c_get_window_is_pinned(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_is_pinned", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ gboolean is_pinned = window ? wnck_window_is_pinned(window) : FALSE;
+
+ lua_pushboolean(lua, is_pinned);
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_set_window_above(lua_State *lua)
+{
+ if (!check_param_counts(lua, "set_window_above", 0, 1)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+ gboolean set_above = TRUE;
+
+ if (top == 1)
+ {
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_window_above: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ int value = lua_toboolean(lua, 1);
+ set_above = (gboolean)(value);
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ if (set_above)
+ wnck_window_make_above(window);
+ else
+ wnck_window_unmake_above(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_set_window_below(lua_State *lua)
+{
+ if (!check_param_counts(lua, "set_window_below", 0, 1)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+ gboolean set_below = TRUE;
+
+ if (top == 1)
+ {
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_window_below: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ int value = lua_toboolean(lua, 1);
+ set_below = (gboolean)(value);
+ }
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ if (set_below)
+ wnck_window_make_below(window);
+ else
+ wnck_window_unmake_below(window);
+ }
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_get_window_type(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_type", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ const char *window_type_string;
+
+ if (window) {
+ WnckWindowType window_type = wnck_window_get_window_type(window);
+
+ switch (window_type) {
+ case WNCK_WINDOW_NORMAL:
+ window_type_string = "WINDOW_TYPE_NORMAL";
+ break;
+ case WNCK_WINDOW_DESKTOP:
+ window_type_string = "WINDOW_TYPE_DESKTOP";
+ break;
+ case WNCK_WINDOW_DOCK:
+ window_type_string = "WINDOW_TYPE_DOCK";
+ break;
+ case WNCK_WINDOW_DIALOG:
+ window_type_string = "WINDOW_TYPE_DIALOG";
+ break;
+ case WNCK_WINDOW_TOOLBAR:
+ window_type_string = "WINDOW_TYPE_TOOLBAR";
+ break;
+ case WNCK_WINDOW_MENU:
+ window_type_string = "WINDOW_TYPE_MENU";
+ break;
+ case WNCK_WINDOW_UTILITY:
+ window_type_string = "WINDOW_TYPE_UTILITY";
+ break;
+ case WNCK_WINDOW_SPLASHSCREEN:
+ window_type_string = "WINDOW_TYPE_SPLASHSCREEN";
+ break;
+ default:
+ window_type_string = "WINDOW_TYPE_UNRECOGNIZED";
+ };
+ } else {
+ window_type_string = "WINDOW_ERROR";
+ }
+
+ lua_pushstring(lua, window_type_string);
+
+ return 1;
+}
+
+
+/**
+ * c_get_class_instance_name
+ * Only available on libwnck 3+
+ */
+int c_get_class_instance_name(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_class_instance_name", 0)) {
+ return 0;
+ }
+
+#ifdef HAVE_GTK3
+ WnckWindow *window = get_current_window();
+ const char *class_instance_name = window ? wnck_window_get_class_instance_name(window) : "";
+
+ // one item returned - the window class instance name as a string.
+ lua_pushstring(lua, class_instance_name);
+#else
+ lua_pushnil(lua);
+#endif
+ return 1;
+}
+
+/**
+ * c_get_class_group_name
+ * Only available on libwnck 3+
+ */
+int c_get_class_group_name(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_class_group_name", 0)) {
+ return 0;
+ }
+
+#ifdef HAVE_GTK3
+ WnckWindow *window = get_current_window();
+ const char *class_group_name = window ? wnck_window_get_class_group_name(window) : "";
+
+ // one item returned - the window class instance name as a string.
+ lua_pushstring(lua, class_group_name);
+#else
+ lua_pushnil(lua);
+#endif
+ return 1;
+}
+
+
+/**
+ *
+ */
+static int c_get_window_property_internal(lua_State *lua, const char *func, gboolean report)
+{
+ if (!check_param_count(lua, "get_window_property", 1)) {
+ return 0;
+ }
+
+ // gchar *property=
+ int ret = 0;
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TSTRING) {
+ luaL_error(lua, "%s: %s", func, string_expected_as_indata_error);
+ return 0;
+ }
+
+ const gchar *value = lua_tostring(lua, 1);
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ gboolean utf8;
+ char *result = my_wnck_get_string_property(wnck_window_get_xid(window), my_wnck_atom_get(value), &utf8);
+
+ if (report & 1) {
+ lua_pushstring(lua, result ? result : "");
+ ret++;
+ }
+ if (report & 2) {
+ lua_pushboolean(lua, utf8);
+ ret++;
+ }
+ g_free (result);
+ } else {
+ //lua_pushstring(lua, "NO RESULT");
+ lua_pushnil(lua);
+ ret++;
+ }
+
+ return ret;
+}
+
+int c_get_window_property(lua_State *lua)
+{
+ return c_get_window_property_internal(lua, "get_window_property", 1);
+}
+
+int c_window_property_is_utf8(lua_State *lua)
+{
+ return c_get_window_property_internal(lua, "window_property_is_utf8", 2);
+}
+
+int c_get_window_property_full(lua_State *lua)
+{
+ return c_get_window_property_internal(lua, "get_window_property_full", 3);
+}
+
+
+/**
+ *
+ */
+int c_set_window_property(lua_State *lua)
+{
+ if (!check_param_counts(lua, "set_window_property", 2, 3)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+ WnckWindow *window = get_current_window();
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TSTRING) {
+ luaL_error(lua, "set_window_property: %s", string_expected_as_indata_error);
+ return 0;
+ }
+
+ const gchar *property = lua_tostring(lua, 1);
+
+ type = lua_type(lua, 2);
+
+ switch (type) {
+ case LUA_TSTRING:
+ gboolean use_utf8 = default_use_utf8;
+ if (top > 2) {
+ type = lua_type(lua, 3);
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_window_property: %s", boolean_expected_as_indata_error);
+ return 0;
+ }
+ use_utf8 = lua_toboolean(lua, 3);
+ }
+ if (!devilspie2_emulate && window)
+ my_wnck_set_string_property(wnck_window_get_xid(window), my_wnck_atom_get(property),
+ lua_tostring(lua, 2), use_utf8);
+ break;
+
+ case LUA_TNUMBER:
+ if (!devilspie2_emulate && window)
+ my_wnck_set_cardinal_property(wnck_window_get_xid(window), my_wnck_atom_get(property),
+ (int32_t) lua_tonumber(lua, 2));
+ break;
+
+ case LUA_TBOOLEAN:
+ if (!devilspie2_emulate && window)
+ my_wnck_set_cardinal_property(wnck_window_get_xid(window), my_wnck_atom_get(property),
+ (int32_t) lua_toboolean(lua, 2));
+ break;
+
+ default:
+ luaL_error(lua, "set_window_property: %s", number_or_string_or_boolean_expected_as_indata_error);
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_delete_window_property(lua_State *lua)
+{
+ if (!check_param_count(lua, "delete_window_property", 1)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TSTRING) {
+ luaL_error(lua, "del_window_property: %s", string_expected_as_indata_error);
+ return 0;
+ }
+
+ const gchar *property = lua_tostring(lua, 1);
+
+ if (!devilspie2_emulate)
+ my_wnck_delete_property(wnck_window_get_xid(window), my_wnck_atom_get(property));
+
+ return 0;
+}
+/**
+ *
+ */
+int c_get_window_role(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_role", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ char *result = my_wnck_get_string_property(wnck_window_get_xid(window), my_wnck_atom_get("WM_WINDOW_ROLE"), NULL);
+
+ lua_pushstring(lua, result ? result : "");
+ g_free (result);
+ } else {
+ lua_pushstring(lua, "");
+ }
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_get_window_xid(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_xid", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ gulong result = window ? wnck_window_get_xid(window) : 0;
+
+ lua_pushinteger(lua, result);
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_get_window_class(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_class", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ const char *result = "";
+
+ if (window) {
+ WnckClassGroup *class_group = wnck_window_get_class_group(window);
+ if (class_group) {
+#ifdef WNCK_MAJOR_VERSION
+#if WNCK_CHECK_VERSION(3,2,0)
+ result = (char*)wnck_class_group_get_id(class_group);
+#else
+ result = (char*)wnck_class_group_get_res_class (class_group);
+#endif
+#else
+ result = (char*)wnck_class_group_get_res_class (class_group);
+#endif
+ }
+ }
+
+ lua_pushstring(lua, result);
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_set_window_fullscreen(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_window_fullscreen", 1)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ int type = lua_type(lua, 1);
+
+ if (type != LUA_TBOOLEAN) {
+ luaL_error(lua, "set_window_fullscreen: %s",
+ boolean_expected_as_indata_error);
+ return 0;
+ }
+
+ gboolean fullscreen = lua_toboolean(lua, 1);
+
+ if (!devilspie2_emulate && window) {
+ wnck_window_set_fullscreen(window, fullscreen);
+ }
+
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_set_viewport(lua_State *lua)
+{
+ if (!check_param_counts(lua, "set_viewport", 1, 2)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+ int width, height;
+ int viewport_start_x, viewport_start_y;
+ int win_x, win_y;
+ gulong xid;
+ WnckWindow *window;
+
+ switch (top)
+ {
+ case 1:
+ WnckScreen *screen;
+ int x;
+ int type = lua_type(lua, 1);
+ if (type != LUA_TNUMBER) {
+ luaL_error(lua, "set_viewport: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ int num = lua_tonumber(lua,1);
+
+ if (num <= 0) {
+ g_error("set_viewport: %s", integer_greater_than_zero_expected_error);
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ window = get_current_window();
+
+ if (!window) {
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ screen = wnck_window_get_screen(window);
+
+ wnck_window_get_geometry(window, &win_x, &win_y, &width, &height);
+
+ xid = wnck_window_get_xid(window);
+
+ //viewport_start = devilspie2_get_viewport_start(xid);
+ if (devilspie2_get_viewport_start(xid, &viewport_start_x, &viewport_start_y) != 0) {
+ g_printerr("set_viewport: %s", could_not_find_current_viewport_error);
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ x = ((num - 1) * wnck_screen_get_width(screen)) - viewport_start_x + win_x;
+
+ if (!devilspie2_emulate) {
+ devilspie2_error_trap_push();
+ XMoveResizeWindow(gdk_x11_get_default_xdisplay(),
+ wnck_window_get_xid(window),
+ x, win_y, width, height);
+
+ if (devilspie2_error_trap_pop()) {
+ g_printerr("set_viewport: %s", setting_viewport_failed_error);
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+ }
+
+ lua_pushboolean(lua, TRUE);
+ return 1;
+ case 2:
+ int type1 = lua_type(lua, 1);
+ int type2 = lua_type(lua, 2);
+
+ if (type1 != LUA_TNUMBER) {
+ luaL_error(lua, "set_viewport: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ if (type2 != LUA_TNUMBER) {
+ luaL_error(lua, "set_viewport: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ int new_xpos = lua_tonumber(lua, 1);
+ int new_ypos = lua_tonumber(lua, 2);
+
+ window = get_current_window();
+
+ if (!window) {
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ wnck_window_get_geometry(window, &win_x, &win_y, &width, &height);
+
+ xid = wnck_window_get_xid(window);
+
+ //viewport_start = devilspie2_get_viewport_start(xid);
+ if (devilspie2_get_viewport_start(xid, &viewport_start_x, &viewport_start_y) != 0) {
+ g_printerr("set_viewport: %s", could_not_find_current_viewport_error);
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ if (!devilspie2_emulate) {
+ devilspie2_error_trap_push();
+ XMoveResizeWindow(gdk_x11_get_default_xdisplay(),
+ wnck_window_get_xid(window),
+ new_xpos, new_ypos, width, height);
+
+ if (devilspie2_error_trap_pop()) {
+ g_printerr("set_viewport: %s", setting_viewport_failed_error);
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+ }
+
+ lua_pushboolean(lua, TRUE);
+ return 1;
+ }
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_center(lua_State *lua)
+{
+ if (!check_param_counts_range(lua, "center", 0, 2)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+
+ GdkRectangle desktop_r, window_r;
+
+ WnckWindow *window = get_current_window();
+
+ if (!window) {
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ wnck_window_get_geometry(window, &window_r.x, &window_r.y, &window_r.width, &window_r.height);
+
+ int monitor_no = MONITOR_ALL;
+ enum { CENTRE_NONE, CENTRE_H, CENTRE_V, CENTRE_HV } centre = CENTRE_HV;
+
+ for (int i = 1; i <= top; ++i) {
+ int type = lua_type(lua, i);
+ gchar *indata;
+
+ switch (type) {
+ case LUA_TNUMBER:
+ monitor_no = lua_tonumber(lua, i) - 1;
+ if (monitor_no < MONITOR_ALL || monitor_no >= get_monitor_count())
+ monitor_no = MONITOR_WINDOW; // FIXME: primary monitor; show warning?
+ break;
+ case LUA_TSTRING:
+ indata = (gchar*)lua_tostring(lua, i);
+ switch (*indata & 0xDF) {
+ case 'H':
+ centre = CENTRE_H;
+ break;
+ case 'V':
+ centre = CENTRE_V;
+ break;
+ default:
+ centre = CENTRE_HV;
+ }
+ break;
+ default:
+ luaL_error(lua, "center: %s", number_or_string_expected_as_indata_error);
+ }
+ }
+
+ monitor_no = get_monitor_or_workspace_geometry(monitor_no, window, &desktop_r);
+ if (monitor_no == MONITOR_NONE) {
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+
+ if (centre & 1)
+ window_r.x = desktop_r.x + (desktop_r.width - window_r.width) / 2;
+ else if (window_r.x < desktop_r.x)
+ window_r.x = desktop_r.x;
+ else if (window_r.x + window_r.width >= desktop_r.x + desktop_r.width)
+ window_r.x = desktop_r.x + desktop_r.width - window_r.width;
+
+ if (centre & 2)
+ window_r.y = desktop_r.y + (desktop_r.height - window_r.height) / 2;
+ else if (window_r.y < desktop_r.y)
+ window_r.y = desktop_r.y;
+ else if (window_r.y + window_r.height >= desktop_r.y + desktop_r.height)
+ window_r.y = desktop_r.y + desktop_r.height - window_r.height;
+
+ if (!devilspie2_emulate) {
+ devilspie2_error_trap_push();
+ XMoveWindow (gdk_x11_get_default_xdisplay(),
+ wnck_window_get_xid(window),
+ window_r.x, window_r.y);
+
+ if (devilspie2_error_trap_pop()) {
+ g_printerr("center: %s", failed_string);
+ lua_pushboolean(lua, FALSE);
+ return 1;
+ }
+ }
+
+ lua_pushboolean(lua, TRUE);
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_set_window_opacity(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_window_opacity", 1)) {
+ return 0;
+ }
+
+
+ //WnckScreen *screen;
+
+ int type = lua_type(lua, 1);
+ if (type != LUA_TNUMBER) {
+ luaL_error(lua, "set_opacity: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ double value = (double)lua_tonumber(lua, 1);
+ WnckWindow *window = get_current_window();
+
+ if (!devilspie2_emulate && window) {
+ gulong xid = wnck_window_get_xid(window);
+ my_window_set_opacity(xid, value);
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_set_window_type(lua_State *lua)
+{
+ if (!check_param_count(lua, "set_window_type", 1)) {
+ return 0;
+ }
+
+ int type = lua_type(lua, 1);
+ if (type != LUA_TSTRING) {
+ luaL_error(lua, "set_window_type: %s", string_expected_as_indata_error);
+ return 0;
+ }
+
+ gchar *indata = (gchar*)lua_tostring(lua, 1);
+
+ WnckWindow *window = get_current_window();
+
+ if (!devilspie2_emulate && window) {
+ gulong xid = wnck_window_get_xid(window);
+ my_window_set_window_type(xid, indata);
+ }
+
+ return 0;
+}
+
+
+/**
+ * return the geometry of the screen to the Lua script
+ */
+int c_get_screen_geometry(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_screen_geometry", 0)) {
+ return 0;
+ }
+
+ int width = -1, height = -1;
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ WnckScreen *screen;
+ screen = wnck_window_get_screen(window);
+ width = wnck_screen_get_width(screen);
+ height = wnck_screen_get_height(screen);
+ }
+
+ lua_pushinteger(lua, width);
+ lua_pushinteger(lua, height);
+
+ return 2;
+}
+
+
+/**
+ *
+ */
+int c_focus(lua_State *lua)
+{
+ if (!check_param_count(lua, "focus", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (!devilspie2_emulate && window) {
+ wnck_window_activate(window, current_time());
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_close_window(lua_State *lua)
+{
+ if (!check_param_count(lua, "close_window", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (!devilspie2_emulate && window) {
+ wnck_window_close(window, current_time());
+ }
+
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_get_window_fullscreen(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_window_fullscreen", 0)) {
+ return 0;
+ }
+
+ gboolean result = FALSE;
+
+ WnckWindow *window = get_current_window();
+ if (window) {
+ result = wnck_window_is_fullscreen(window);
+ }
+
+ lua_pushboolean(lua, result);
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_get_monitor_index(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_monitor_index", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+ if (window) {
+ int index = get_monitor_index_geometry(window, NULL, NULL);
+ if (index < 0)
+ index = -1; // invalid? assume single monitor
+ lua_pushinteger(lua, index + 1);
+ }
+
+ return 1;
+}
+
+
+/**
+ *
+ */
+int c_get_monitor_geometry(lua_State *lua)
+{
+ if (!check_param_counts(lua, "get_monitor_geometry", 0, 1)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+
+ GdkRectangle geom;
+
+ if (top == 0) {
+ WnckWindow *window = get_current_window();
+ if (!window)
+ return 1; // =nil
+
+ get_monitor_index_geometry(window, NULL, &geom);
+
+ } else if (top == 1) {
+ int type = lua_type(lua, 1);
+
+ if (type!=LUA_TNUMBER) {
+ luaL_error(lua, "get_monitor_geometry: %s",
+ number_expected_as_indata_error);
+ return 0;
+ }
+
+ int index = lua_tonumber(lua, 1) - 1;
+ int actual = get_monitor_geometry(index, &geom);
+
+ if (actual != index)
+ return 0;
+ }
+
+ lua_pushinteger(lua, geom.x);
+ lua_pushinteger(lua, geom.y);
+ lua_pushinteger(lua, geom.width);
+ lua_pushinteger(lua, geom.height);
+
+ return 4;
+}
+
+
+/**
+ *
+ */
+int c_xy(lua_State *lua)
+{
+ if (!check_param_counts(lua, "xy", 0, 2)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+
+ switch (top)
+ {
+ case 0:
+ // return the xy coordinates of the window
+
+ WnckWindow *window = get_current_window();
+ if (window) {
+
+ int x, y, width, height;
+
+ wnck_window_get_geometry(window, &x, &y, &width, &height);
+
+ lua_pushinteger(lua, x);
+ lua_pushinteger(lua, y);
+
+ return 2;
+ }
+ break;
+ case 2:
+ // set the coordinates of the window
+
+ int type1 = lua_type(lua, 1);
+ int type2 = lua_type(lua, 2);
+
+ if ((type1 != LUA_TNUMBER) ||
+ (type2 != LUA_TNUMBER)) {
+ luaL_error(lua, "xy: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ int x = lua_tonumber(lua, 1);
+ int y = lua_tonumber(lua, 2);
+
+ if (!devilspie2_emulate) {
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ if (adjusting_for_decoration)
+ adjust_for_decoration (window, &x, &y, NULL, NULL);
+ wnck_window_set_geometry(window,
+ WNCK_WINDOW_GRAVITY_CURRENT,
+ WNCK_WINDOW_CHANGE_X + WNCK_WINDOW_CHANGE_Y,
+ x, y, -1, -1);
+ }
+ }
+ break;
+ }
+ return 0;
+}
+
+
+/**
+ *
+ */
+int c_xywh(lua_State *lua)
+{
+ if (!check_param_counts(lua, "xywh", 0, 4)) {
+ return 0;
+ }
+
+ int top = lua_gettop(lua);
+
+ switch (top)
+ {
+ case 0:
+ // Return the xywh settings of the window
+
+ WnckWindow *window = get_current_window();
+ if (window) {
+
+ int x, y, width, height;
+
+ wnck_window_get_geometry(window, &x, &y, &width, &height);
+
+ lua_pushinteger(lua, x);
+ lua_pushinteger(lua, y);
+ lua_pushinteger(lua, width);
+ lua_pushinteger(lua, height);
+
+ return 4;
+ }
+ break;
+ case 4:
+ // Set the xywh settings in the window
+
+
+ int type1 = lua_type(lua, 1);
+ int type2 = lua_type(lua, 2);
+ int type3 = lua_type(lua, 3);
+ int type4 = lua_type(lua, 4);
+
+ if ((type1 != LUA_TNUMBER) ||
+ (type2 != LUA_TNUMBER) ||
+ (type3 != LUA_TNUMBER) ||
+ (type4 != LUA_TNUMBER)) {
+ luaL_error(lua, "xywh: %s", number_expected_as_indata_error);
+ return 0;
+ }
+
+ int x = lua_tonumber(lua, 1);
+ int y = lua_tonumber(lua, 2);
+ int xsize = lua_tonumber(lua, 3);
+ int ysize = lua_tonumber(lua, 4);
+
+ if (!devilspie2_emulate) {
+ WnckWindow *window = get_current_window();
+ if (window) {
+ set_window_geometry(window, x, y, xsize, ysize, adjusting_for_decoration);
+ }
+ }
+
+ return 0;
+ }
+ return 0;
+}
+
+struct lua_callback {
+ lua_State *lua;
+ int ref;
+};
+
+static void on_geometry_changed(WnckWindow *window, struct lua_callback *callback)
+{
+ if (callback == NULL)
+ return;
+
+ WnckWindow *old_window = get_current_window();
+ set_current_window(window);
+
+ lua_rawgeti(callback->lua, LUA_REGISTRYINDEX, callback->ref);
+ lua_pcall(callback->lua, 0, 0, 0);
+
+ set_current_window(old_window);
+}
+
+static void on_geometry_changed_disconnect(gpointer data, GClosure *closure G_GNUC_UNUSED)
+{
+ g_free(data);
+}
+
+/**
+ *
+ */
+int c_on_geometry_changed(lua_State *lua)
+{
+ if (!check_param_count(lua, "on_geometry_changed", 1)) {
+ return 0;
+ }
+
+ if (lua_type(lua, 1) != LUA_TFUNCTION) {
+ luaL_error(lua, "on_geometry_changed: %s", "function expected");
+ return 0;
+ }
+
+ struct lua_callback *cb = g_malloc(sizeof(struct lua_callback));
+ cb->lua = lua;
+ cb->ref = luaL_ref(lua, LUA_REGISTRYINDEX);
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ g_signal_connect_data(window, "geometry-changed", G_CALLBACK(on_geometry_changed), (gpointer)cb, (GClosureNotify)(on_geometry_changed_disconnect), 0);
+ }
+
+ return 0;
+}
+
+/**
+ * returns the process binary name
+ */
+static ATTR_MALLOC gchar *c_get_process_name_INT_proc(lua_State *, pid_t);
+static ATTR_MALLOC gchar *c_get_process_name_INT_ps(lua_State *, pid_t);
+
+int c_get_process_name(lua_State *lua)
+{
+ if (!check_param_count(lua, "get_process_name", 0)) {
+ return 0;
+ }
+
+ WnckWindow *window = get_current_window();
+
+ if (window) {
+ pid_t pid = wnck_window_get_pid(window);
+
+ if (pid != 0) {
+ gchar *cmdname = c_get_process_name_INT_proc(lua, pid);
+ if (!cmdname)
+ cmdname = c_get_process_name_INT_ps(lua, pid);
+
+ /* chop off any trailing LF */
+ gchar *lf = cmdname + strlen(cmdname) - 1;
+ if (lf >= cmdname && *lf == '\n')
+ *lf = 0;
+
+ lua_pushstring(lua, cmdname ? cmdname : "");
+ g_free(cmdname);
+ return 1;
+ }
+ }
+
+ lua_pushstring(lua, "");
+ return 1;
+}
+
+static gchar *c_get_process_name_INT_proc(lua_State *lua, pid_t pid)
+{
+ /* 16 is fine for cmdname on Linux. Could be longer elsewhere, though. */
+ char cmd[1024], cmdname[1024];
+ FILE *cmdfp;
+
+ cmdname[0] = 0;
+
+ snprintf(cmd, sizeof(cmd), "/proc/%lu/comm", (unsigned long)pid);
+ cmdfp = fopen(cmd, "r");
+ if (cmdfp == NULL) {
+ if (errno != ENOENT && errno != EACCES) {
+ luaL_error(lua, "get_process_name: Failed to open \"%s\" (%d).", cmd, errno);
+ }
+ return NULL;
+ }
+
+ if (fgets(cmdname, sizeof(cmdname), cmdfp) == NULL) {
+ fclose(cmdfp);
+ luaL_error(lua, "get_process_name: Failed to read from \"%s\".", cmd);
+ return NULL;
+ }
+
+ fclose(cmdfp);
+ return g_strdup(cmdname);
+}
+
+static gchar *c_get_process_name_INT_ps(lua_State *lua, pid_t pid)
+{
+ char cmd[1024], cmdname[1024];
+ FILE *cmdfp;
+
+ /* I'd like to use "ps ho comm c %lu" here.
+ * Seems that FreeBSD ps outputs headers regardless.
+ * (Tested using procps 'ps' with PS_PERSONALITY=bsd)
+ */
+ snprintf(cmd, sizeof(cmd), "ps o comm c %lu | tail -n 1", (unsigned long)pid);
+ cmdfp = popen(cmd, "r");
+ if (cmdfp == NULL) {
+ luaL_error(lua, "get_process_name: Failed to run command \"%s\".", cmd);
+ return 0;
+ }
+
+ if (fgets(cmdname, sizeof(cmdname), cmdfp) == NULL) {
+ pclose(cmdfp);
+ luaL_error(lua, "get_process_name: Failed to read output from command \"%s\".", cmd);
+ return 0;
+ }
+
+ pclose(cmdfp);
+ return g_strdup(cmdname);
+}
+
+
+/**
+ *
+ */
+int c_millisleep(lua_State *lua)
+{
+ if (!check_param_count(lua, "millisleep", 1)) {
+ return 0;
+ }
+ if (lua_type(lua, 1) != LUA_TNUMBER) {
+ luaL_error(lua, "millisleep: %s",
+ number_expected_as_indata_error);
+ return 0;
+ }
+
+ int time = lua_tonumber(lua, 1);
+ if (time < 1 || time > 1000) {
+ luaL_error(lua, _("millisleep: time %d out of range (1..1000)"), time);
+ return 0;
+ }
+
+ struct timespec tv;
+ struct timespec left;
+ if (time == 1000) {
+ tv.tv_sec = 1;
+ tv.tv_nsec = 0;
+ } else {
+ tv.tv_sec = 0;
+ tv.tv_nsec = time * 1000000;
+ }
+ while (nanosleep(&tv, &left) && errno == EINTR)
+ tv = left;
+
+ return 0;
+}
+
+
+/*
+ * Devilspie:
+
+ * Focus the current window.
+
+ESExpResult *func_focus(ESExp *f, int argc, ESExpResult **argv, Context *c) {
+ wnck_window_activate (c->window, current_time());
+ if (debug) g_printerr (_("Focusing\n"));
+ return e_sexp_result_new_bool (f, TRUE);
+}
+
+*/
diff --git a/devilspie/script_functions.h b/devilspie/script_functions.h
new file mode 100644
index 0000000..6a2f2d0
--- /dev/null
+++ b/devilspie/script_functions.h
@@ -0,0 +1,148 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2011-2019 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HEADER_SCRIPT_FUNCTIONS_
+#define __HEADER_SCRIPT_FUNCTIONS_
+
+/**
+ *
+ */
+#include "lua.h"
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include "libwnck/libwnck.h"
+
+int c_use_utf8(lua_State *lua);
+
+int c_get_window_name(lua_State *lua);
+int c_get_window_has_name(lua_State *lua);
+
+int c_set_window_position(lua_State *lua);
+int c_set_window_position2(lua_State *lua);
+
+int c_set_window_geometry(lua_State *lua);
+int c_set_window_geometry2(lua_State *lua);
+
+int c_set_window_size(lua_State *lua);
+
+int c_set_window_strut(lua_State *lua);
+int c_get_window_strut(lua_State *lua);
+
+int c_get_application_name(lua_State *lua);
+
+int c_debug_print(lua_State *lua);
+
+int c_shade(lua_State *lua);
+int c_unshade(lua_State *lua);
+
+int c_minimize(lua_State *lua);
+int c_unminimize(lua_State *lua);
+
+int c_decorate_window(lua_State *lua);
+int c_undecorate_window(lua_State *lua);
+int c_get_window_is_decorated(lua_State *lua);
+
+int c_set_window_workspace(lua_State *lua);
+int c_change_workspace(lua_State *lua);
+int c_get_workspace_count(lua_State *lua);
+
+int c_unmaximize(lua_State *lua);
+int c_maximize(lua_State *lua);
+int c_maximize_vertically(lua_State *lua);
+int c_maximize_horisontally(lua_State *lua); // deprecated
+int c_maximize_horizontally(lua_State *lua);
+
+int c_pin_window(lua_State *lua);
+int c_unpin_window(lua_State *lua);
+int c_stick_window(lua_State *lua);
+int c_unstick_window(lua_State *lua);
+
+int c_close_window(lua_State *lua);
+
+void set_current_window(WnckWindow *window);
+WnckWindow *get_current_window();
+
+int c_set_adjust_for_decoration(lua_State *lua);
+
+int c_get_window_geometry(lua_State *lua);
+int c_get_window_client_geometry(lua_State *lua);
+int c_get_window_frame_extents(lua_State *lua);
+
+int c_set_skip_tasklist(lua_State *lua);
+int c_set_skip_pager(lua_State *lua);
+
+int c_get_window_is_maximized(lua_State *lua);
+int c_get_window_is_maximized_vertically(lua_State *lua);
+int c_get_window_is_maximized_horisontally(lua_State *lua); // deprecated
+int c_get_window_is_maximized_horizontally(lua_State *lua);
+int c_get_window_is_pinned(lua_State *lua);
+
+int c_set_window_fullscreen(lua_State *lua);
+
+int c_set_window_above(lua_State *lua);
+int c_set_window_below(lua_State *lua);
+
+int c_make_always_on_top(lua_State *lua);
+int c_set_on_top(lua_State *lua);
+int c_set_on_bottom(lua_State *lua);
+
+int c_get_window_type(lua_State *lua);
+
+// these two require GTK 3 or later
+int c_get_class_instance_name(lua_State *lua);
+int c_get_class_group_name(lua_State *lua);
+
+int c_get_window_property(lua_State *lua);
+int c_window_property_is_utf8(lua_State *lua);
+int c_get_window_property_full(lua_State *lua);
+int c_get_window_role(lua_State *lua);
+
+int c_get_window_xid(lua_State *lua);
+
+int c_get_window_class(lua_State *lua);
+
+int c_set_window_property(lua_State *lua);
+int c_delete_window_property(lua_State *lua);
+
+int c_set_viewport(lua_State *lua);
+
+int c_center(lua_State *lua);
+
+int c_set_window_opacity(lua_State *lua);
+int c_set_window_type(lua_State *lua);
+
+
+int c_get_screen_geometry(lua_State *lua);
+
+int c_focus(lua_State *lua);
+
+int c_get_window_fullscreen(lua_State *lua);
+
+int c_get_monitor_index(lua_State *lua);
+int c_get_monitor_geometry(lua_State *lua);
+
+int c_xy(lua_State *lua);
+int c_xywh(lua_State *lua);
+
+int c_on_geometry_changed(lua_State *lua);
+
+int c_get_process_name(lua_State *lua);
+
+int c_millisleep(lua_State *lua);
+
+#endif /*__HEADER_SCRIPT_FUNCTIONS_*/
diff --git a/devilspie/xutils.c b/devilspie/xutils.c
new file mode 100644
index 0000000..19a698b
--- /dev/null
+++ b/devilspie/xutils.c
@@ -0,0 +1,779 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2001 Havoc Pennington, 2011-2019 Andreas Rönnquist
+ * Copyright (C) 2019-2021 Darren Salt
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+#include <X11/Xatom.h>
+
+#include <glib.h>
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+#include <X11/Xlib.h>
+#include <string.h>
+
+// FIXME: retrieve screen position via wnck
+#include <X11/extensions/Xinerama.h>
+
+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
+#include <libwnck/libwnck.h>
+
+#include <locale.h>
+
+#include "intl.h"
+#include "xutils.h"
+
+
+#if (GTK_MAJOR_VERSION >= 3)
+#define HAVE_GTK3
+#endif
+
+
+static GHashTable *atom_hash = NULL;
+static GHashTable *reverse_atom_hash = NULL;
+
+
+/**
+ *
+ */
+Atom my_wnck_atom_get(const char *atom_name)
+{
+ Atom retval;
+
+ g_return_val_if_fail (atom_name != NULL, None);
+
+ if (!atom_hash) {
+ atom_hash = g_hash_table_new (g_str_hash, g_str_equal);
+ reverse_atom_hash = g_hash_table_new (NULL, NULL);
+ }
+
+ retval = GPOINTER_TO_UINT (g_hash_table_lookup (atom_hash, atom_name));
+ if (!retval) {
+ retval = XInternAtom (gdk_x11_get_default_xdisplay(), atom_name, FALSE);
+
+ if (retval != None) {
+ char *name_copy;
+
+ name_copy = g_strdup (atom_name);
+
+ g_hash_table_insert (atom_hash,
+ name_copy,
+ GUINT_TO_POINTER (retval));
+ g_hash_table_insert (reverse_atom_hash,
+ GUINT_TO_POINTER (retval),
+ name_copy);
+ }
+ }
+ return retval;
+}
+
+
+/**
+ *
+ */
+void devilspie2_change_state(Screen *screen, Window xwindow,
+ gboolean add,
+ Atom state1,
+ Atom state2)
+{
+ XEvent xev;
+
+#define _NET_WM_STATE_REMOVE 0 /* remove/unset property */
+#define _NET_WM_STATE_ADD 1 /* add/set property */
+#define _NET_WM_STATE_TOGGLE 2 /* toggle property */
+
+ xev.xclient.type = ClientMessage;
+ xev.xclient.serial = 0;
+ xev.xclient.send_event = True;
+ xev.xclient.display = gdk_x11_get_default_xdisplay();
+ xev.xclient.window = xwindow;
+ xev.xclient.message_type = my_wnck_atom_get ("_NET_WM_STATE");
+ xev.xclient.format = 32;
+ xev.xclient.data.l[0] = add ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
+ xev.xclient.data.l[1] = state1;
+ xev.xclient.data.l[2] = state2;
+
+ XSendEvent (gdk_x11_get_default_xdisplay(),
+ RootWindowOfScreen (screen),
+ False,
+ SubstructureRedirectMask | SubstructureNotifyMask,
+ &xev);
+}
+
+
+/**
+ *
+ */
+void devilspie2_error_trap_push()
+{
+#if GTK_CHECK_VERSION(3, 0, 0)
+ gdk_x11_display_error_trap_push(gdk_display_get_default());
+#else
+ gdk_error_trap_push();
+#endif
+}
+
+
+/**
+ *
+ */
+int devilspie2_error_trap_pop()
+{
+#if GTK_CHECK_VERSION(3, 0, 0)
+ return gdk_x11_display_error_trap_pop(gdk_display_get_default());
+#else
+ XSync(gdk_x11_get_default_xdisplay(),False);
+ return gdk_error_trap_pop();
+#endif
+}
+
+
+/**
+ *
+ */
+static void set_decorations(Window xid /*WnckWindow *window*/, gboolean decorate)
+{
+#define PROP_MOTIF_WM_HINTS_ELEMENTS 5
+#define MWM_HINTS_DECORATIONS (1L << 1)
+ struct {
+ unsigned long flags;
+ unsigned long functions;
+ unsigned long decorations;
+ long inputMode;
+ unsigned long status;
+ } hints = {0,};
+
+ hints.flags = MWM_HINTS_DECORATIONS;
+ hints.decorations = decorate ? 1 : 0;
+
+ /* Set Motif hints, most window managers handle these */
+ XChangeProperty(gdk_x11_get_default_xdisplay(), xid /*wnck_window_get_xid (window)*/,
+ my_wnck_atom_get ("_MOTIF_WM_HINTS"),
+ my_wnck_atom_get ("_MOTIF_WM_HINTS"), 32, PropModeReplace,
+ (unsigned char *)&hints, PROP_MOTIF_WM_HINTS_ELEMENTS);
+
+
+ //Window xid;
+ XWindowAttributes attrs;
+
+ //xid = wnck_window_get_xid (window);
+ XGetWindowAttributes(gdk_x11_get_default_xdisplay(), xid, &attrs);
+
+ /* Apart from OpenBox, which doesn't respect it changing after mapping.
+ * Instead it has this workaround.
+ */
+ devilspie2_change_state (attrs.screen,
+ xid /*wnck_window_get_xid(window)*/, !decorate,
+ my_wnck_atom_get ("_OB_WM_STATE_UNDECORATED"), 0);
+
+}
+
+
+/**
+ *
+ */
+gboolean decorate_window(Window xid)
+{
+ devilspie2_error_trap_push();
+
+ set_decorations(xid, TRUE);
+
+ if (devilspie2_error_trap_pop()) {
+ g_printerr("decorate_window %s\n", _("Failed!"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ *
+ */
+gboolean undecorate_window(Window xid)
+{
+ devilspie2_error_trap_push();
+
+ set_decorations(xid, FALSE);
+
+ if (devilspie2_error_trap_pop()) {
+ g_printerr("decorate_window %s\n", _("Failed!"));
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+/**
+ *
+ */
+gboolean get_decorated(Window xid /*WnckWindow *window*/)
+{
+ Display *disp = gdk_x11_get_default_xdisplay();
+ Atom type_ret;
+ Atom hints_atom = XInternAtom(disp, "_MOTIF_WM_HINTS", False);
+ int format_ret;
+ int err, result = 0;
+ unsigned long nitems_ret, bytes_after_ret, *prop_ret;
+
+ devilspie2_error_trap_push();
+ XGetWindowProperty(disp, xid, hints_atom, 0,
+ PROP_MOTIF_WM_HINTS_ELEMENTS, 0, hints_atom,
+ &type_ret, &format_ret, &nitems_ret,
+ &bytes_after_ret, (unsigned char **)&prop_ret);
+
+ err = devilspie2_error_trap_pop ();
+ if (err != Success || result != Success)
+ return FALSE;
+
+ return type_ret != hints_atom || nitems_ret < 3 || prop_ret[2] != 0;
+}
+
+
+/**
+ *
+ */
+Screen *devilspie2_window_get_xscreen(Window xid)
+{
+ XWindowAttributes attrs;
+
+ XGetWindowAttributes(gdk_x11_get_default_xdisplay(), xid, &attrs);
+
+ return attrs.screen;
+}
+
+
+/**
+ *
+ */
+char* my_wnck_get_string_property(Window xwindow, Atom atom, gboolean *utf8)
+{
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ unsigned char *property;
+ int err, result;
+ char *retval;
+ Atom XA_UTF8_STRING;
+ gboolean is_utf8 = True;
+
+ if (utf8)
+ *utf8 = False;
+
+ devilspie2_error_trap_push();
+ property = NULL;
+ result = XGetWindowProperty (gdk_x11_get_default_xdisplay (),
+ xwindow, atom,
+ 0, G_MAXLONG,
+ False, AnyPropertyType, &type,
+ &format, &nitems,
+ &bytes_after, &property);
+
+ err = devilspie2_error_trap_pop ();
+ if (err != Success || result != Success)
+ return NULL;
+
+ retval = NULL;
+ XA_UTF8_STRING = XInternAtom(gdk_x11_get_default_xdisplay(), "UTF8_STRING", False);
+
+ if (utf8)
+ *utf8 = False;
+
+ if (type == XA_STRING) {
+ is_utf8 = False;
+ retval = g_strdup ((char*)property);
+ } else if (type == XA_UTF8_STRING) {
+ retval = g_strdup ((char*)property);
+ } else if (type == XA_ATOM && nitems > 0 && format == 32) {
+ long *pp;
+
+ pp = (long *)property; // we can assume (long *) since format == 32
+ if (nitems == 1) {
+ char* prop_name;
+ prop_name = XGetAtomName (gdk_x11_get_default_xdisplay (), *pp);
+ if (prop_name) {
+ retval = g_strdup (prop_name);
+ XFree (prop_name);
+ }
+ } else {
+ gulong i;
+ char** prop_names;
+
+ prop_names = g_new (char *, nitems + 1);
+ prop_names[nitems] = NULL;
+ for (i=0; i < nitems; i++) {
+ prop_names[i] = XGetAtomName (gdk_x11_get_default_xdisplay (),
+ *pp++);
+ }
+ retval = g_strjoinv (", ", prop_names);
+ for (i=0; i < nitems; i++) {
+ if (prop_names[i]) XFree (prop_names[i]);
+ }
+ g_free (prop_names);
+ }
+ } else if (type == XA_CARDINAL && nitems == 1) {
+ switch(format) {
+ case 32:
+ retval = g_strdup_printf("%lu", *(unsigned long*)property);
+ break;
+ case 16:
+ retval = g_strdup_printf("%u", *(unsigned int*)property);
+ break;
+ case 8:
+ retval = g_strdup_printf("%c", *(unsigned char*)property);
+ break;
+ }
+ } else if (type == XA_WINDOW && nitems == 1) {
+ /* unsinged long is the same format used for XID by libwnck:
+ * https://git.gnome.org/browse/libwnck/tree/libwnck/window.c?h=3.14.0#n763
+ */
+ retval = g_strdup_printf("%lu", (gulong) *(Window *)property);
+ }
+
+ XFree (property);
+ if (utf8)
+ *utf8 = is_utf8;
+ return retval;
+}
+
+
+/**
+ *
+ */
+void my_wnck_set_string_property(Window xwindow, Atom atom, const gchar *const string, gboolean utf8)
+{
+ const unsigned char *const str = (const unsigned char *)string;
+ Display *display = gdk_x11_get_default_xdisplay();
+ Atom type = utf8 ? XInternAtom(display, "UTF8_STRING", False) : XA_STRING;
+
+ devilspie2_error_trap_push();
+ XChangeProperty (display, xwindow, atom, type, 8, PropModeReplace, str, strlen(string));
+ devilspie2_error_trap_pop ();
+}
+
+
+/**
+ *
+ */
+void my_wnck_set_cardinal_property(Window xwindow, Atom atom, int32_t value)
+{
+ devilspie2_error_trap_push();
+ XChangeProperty (gdk_x11_get_default_xdisplay (),
+ xwindow, atom, XA_CARDINAL, 32,
+ PropModeReplace, (unsigned char *)&value, 1);
+ devilspie2_error_trap_pop ();
+}
+
+
+/**
+ *
+ */
+void my_wnck_delete_property(Window xwindow, Atom atom)
+{
+ devilspie2_error_trap_push();
+ XDeleteProperty (gdk_x11_get_default_xdisplay (), xwindow, atom);
+ devilspie2_error_trap_pop ();
+}
+
+
+/**
+ *
+ */
+gboolean
+my_wnck_get_cardinal_list (Window xwindow, Atom atom,
+ gulong **cardinals, int *len)
+{
+ Atom type;
+ int format;
+ gulong nitems;
+ gulong bytes_after;
+ gulong *nums;
+ int err, result;
+
+ *cardinals = NULL;
+ *len = 0;
+
+ devilspie2_error_trap_push();
+ type = None;
+ result = XGetWindowProperty(gdk_x11_get_default_xdisplay (),
+ xwindow,
+ atom,
+ 0, G_MAXLONG,
+ False, XA_CARDINAL, &type, &format, &nitems,
+ &bytes_after, (void*)&nums);
+
+ err = devilspie2_error_trap_pop();
+
+ if ((err != Success) || (result != Success))
+ return FALSE;
+
+ if (type != XA_CARDINAL) {
+ XFree (nums);
+ return FALSE;
+ }
+
+ *cardinals = g_new(gulong, nitems);
+ memcpy(*cardinals, nums, sizeof (gulong) * nitems);
+ *len = nitems;
+
+ XFree(nums);
+
+ return TRUE;
+}
+
+
+/**
+ * Get viewport start coordinates to the x and y integers,
+ * returns 0 on success and non-zero on error.
+ */
+int devilspie2_get_viewport_start(Window xid, int *x, int *y)
+{
+ gulong *list;
+ int len;
+
+ int result = -1;
+
+ my_wnck_get_cardinal_list(RootWindowOfScreen(devilspie2_window_get_xscreen(xid)),
+ my_wnck_atom_get("_NET_DESKTOP_VIEWPORT"),
+ &list, &len);
+
+ if (len > 0) {
+ *x = list[0];
+ *y = list[1];
+
+ result = 0;
+ }
+
+ g_free(list);
+
+ return result;
+}
+
+
+/**
+ *
+ */
+void my_window_set_window_type(Window xid, gchar *window_type)
+{
+ Display *display = gdk_x11_get_default_xdisplay();
+
+ Atom atoms[10];
+
+ /*
+ _NET_WM_WINDOW_TYPE_DESKTOP, ATOM
+ _NET_WM_WINDOW_TYPE_DOCK, ATOM
+ _NET_WM_WINDOW_TYPE_TOOLBAR, ATOM
+ _NET_WM_WINDOW_TYPE_MENU, ATOM
+ _NET_WM_WINDOW_TYPE_UTILITY, ATOM
+ _NET_WM_WINDOW_TYPE_SPLASH, ATOM
+ _NET_WM_WINDOW_TYPE_DIALOG, ATOM
+ _NET_WM_WINDOW_TYPE_NORMAL, ATOM
+ */
+
+ gchar *type = NULL;
+
+ // Make it a recognized _NET_WM_TYPE
+
+ if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_DESKTOP") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_DESKTOP");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_DOCK") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_DOCK");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_TOOLBAR") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_TOOLBAR");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_MENU") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_MENU");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_UTILITY") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_UTILITY");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_SPLASH") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_SPLASH");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_DIALOG") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_DIALOG");
+
+ } else if (g_ascii_strcasecmp(window_type, "WINDOW_TYPE_NORMAL") == 0) {
+ type = g_strdup("_NET_WM_WINDOW_TYPE_NORMAL");
+
+ } else {
+ type = g_strdup(window_type);
+ }
+
+ atoms[0] = XInternAtom(display, type, False);
+
+ XChangeProperty(gdk_x11_get_default_xdisplay(), xid,
+ XInternAtom(display, "_NET_WM_WINDOW_TYPE", False), XA_ATOM, 32,
+ PropModeReplace, (unsigned char *) &atoms, 1);
+
+ g_free(type);
+}
+
+
+/**
+ *
+ */
+void my_window_set_opacity(Window xid, double value)
+{
+ Display *display = gdk_x11_get_default_xdisplay();
+
+ unsigned int opacity = (uint)(0xffffffff * value);
+ Atom atom_net_wm_opacity = XInternAtom(display, "_NET_WM_WINDOW_OPACITY", False);
+
+
+ XChangeProperty(gdk_x11_get_default_xdisplay(), xid,
+ atom_net_wm_opacity, XA_CARDINAL, 32,
+ PropModeReplace, (unsigned char *) &opacity, 1L);
+
+}
+
+
+/**
+ *
+ */
+void adjust_for_decoration(WnckWindow *window, int *x, int *y, int *w, int *h)
+{
+ GdkRectangle geom, geom_undec;
+
+ wnck_window_get_geometry(window, &geom.x, &geom.y, &geom.width, &geom.height);
+ wnck_window_get_client_window_geometry(window, &geom_undec.x, &geom_undec.y, &geom_undec.width, &geom_undec.height);
+
+ if (x) *x -= geom_undec.x - geom.x;
+ if (y) *y -= geom_undec.y - geom.y;
+ if (w) *w -= geom_undec.width - geom.width;
+ if (h) *h -= geom_undec.height - geom.height;
+}
+
+
+/**
+ *
+ */
+void set_window_geometry(WnckWindow *window, int x, int y, int w, int h, gboolean adjusting_for_decoration)
+{
+ if (window) {
+ WnckScreen *screen = wnck_window_get_screen(window);
+ int sw = wnck_screen_get_width(screen);
+ int sh = wnck_screen_get_height(screen);
+
+ int gravity = WNCK_WINDOW_GRAVITY_CURRENT;
+ if (x >= 0 && y >= 0)
+ gravity = WNCK_WINDOW_GRAVITY_NORTHWEST;
+ if (x >= 0 && y < 0)
+ gravity = WNCK_WINDOW_GRAVITY_SOUTHWEST;
+ if (x < 0 && y >= 0)
+ gravity = WNCK_WINDOW_GRAVITY_NORTHEAST;
+ if (x < 0 && y < 0)
+ gravity = WNCK_WINDOW_GRAVITY_SOUTHEAST;
+ if (x < 0)
+ x = sw + x;
+ if (y < 0)
+ y = sh + y;
+
+ if (adjusting_for_decoration)
+ adjust_for_decoration(window, &x, &y, &w, &h);
+
+ wnck_window_set_geometry(window,
+ gravity,
+ WNCK_WINDOW_CHANGE_X +
+ WNCK_WINDOW_CHANGE_Y +
+ WNCK_WINDOW_CHANGE_WIDTH +
+ WNCK_WINDOW_CHANGE_HEIGHT,
+ x, y, w, h);
+ }
+
+}
+
+
+/**
+ *
+ */
+int get_monitor_count(void)
+{
+ // FIXME: retrieve monitor count via wnck
+ // For now, use Xinerama directly
+ Display *dpy = gdk_x11_get_default_xdisplay();
+
+ if (!XineramaIsActive(dpy))
+ return 0;
+
+ // Normally, we'd use the return value, but we only want the number of entries
+ int monitor_count = 0;
+ XineramaQueryScreens(dpy, &monitor_count);
+
+ return monitor_count;
+}
+
+
+/**
+ *
+ */
+int get_monitor_index_geometry(WnckWindow *window, const GdkRectangle *window_r_in, GdkRectangle *monitor_r)
+{
+ // monitor_r is always filled in unless the return value is -1
+
+ // FIXME: retrieve monitor info via wnck
+ // For now, use Xinerama directly
+ int id = -1;
+ int monitor_count = 0;
+ XineramaScreenInfo *monitor_list = NULL;
+ Display *dpy = gdk_x11_get_default_xdisplay();
+
+ if (XineramaIsActive(dpy))
+ monitor_list = XineramaQueryScreens(dpy, &monitor_count);
+
+ // bail out if no Xinermama or no monitors
+ if (!monitor_list || !monitor_count)
+ return -1;
+
+ // find which monitor the window's centre is on
+ GdkRectangle window_r;
+ if (window)
+ wnck_window_get_geometry(window, &window_r.x, &window_r.y, &window_r.width, &window_r.height);
+ else
+ window_r = *window_r_in;
+
+ GdkPoint centre = { window_r.x + window_r.width / 2, window_r.y + window_r.height / 2 };
+
+ for (int i = 0; i < monitor_count; ++i) {
+ if (centre.x >= monitor_list[i].x_org &&
+ centre.x < monitor_list[i].x_org + monitor_list[i].width &&
+ centre.y >= monitor_list[i].y_org &&
+ centre.y < monitor_list[i].y_org + monitor_list[i].height) {
+ id = i;
+ break;
+ }
+ }
+
+ // if that fails, try intersection of rectangles
+ // just use the first matching
+ // FIXME?: should find whichever shows most of the window (if tied, closest to window centre)
+ if (id < 0) {
+ for (int i = 0; i < monitor_count; ++i) {
+ GdkRectangle r = {
+ monitor_list[i].x_org, monitor_list[i].y_org,
+ monitor_list[i].x_org + monitor_list[i].width,
+ monitor_list[i].y_org + monitor_list[i].height
+ };
+ if (gdk_rectangle_intersect(&window_r, &r, NULL)) {
+ id = i;
+ break;
+ }
+ }
+ }
+
+ // and if that too fails, use the default
+ if (id < 0)
+ id = 0; // FIXME: primary monitor
+
+ if (monitor_r) {
+ monitor_r->x = monitor_list[id].x_org;
+ monitor_r->y = monitor_list[id].y_org;
+ monitor_r->width = monitor_list[id].width;
+ monitor_r->height = monitor_list[id].height;
+ }
+
+ return id;
+}
+
+
+/**
+ *
+ */
+int get_monitor_geometry(int index, GdkRectangle *monitor_r)
+{
+ // if out of range, output is for monitor 0 (if present) else this:
+ *monitor_r = (GdkRectangle){ 0, 0, 640, 480 };
+
+ // FIXME: retrieve monitor info via wnck
+ // For now, use Xinerama directly
+ int monitor_count = 0;
+ XineramaScreenInfo *monitor_list = NULL;
+ Display *dpy = gdk_x11_get_default_xdisplay();
+
+ if (XineramaIsActive(dpy))
+ monitor_list = XineramaQueryScreens(dpy, &monitor_count);
+
+ // bail out if no Xinermama or no monitors
+ if (!monitor_list || !monitor_count)
+ return -1; // no xinerama!
+
+ // FIXME: default to primary monitor
+ if (index < 0 || index >= monitor_count)
+ index = 0;
+
+ monitor_r->x = monitor_list[index].x_org;
+ monitor_r->y = monitor_list[index].y_org;
+ monitor_r->width = monitor_list[index].width;
+ monitor_r->height = monitor_list[index].height;
+
+ return index;
+}
+
+
+/**
+ *
+ */
+int get_window_workspace_geometry(WnckWindow *window, GdkRectangle *geom)
+{
+ WnckScreen *screen = wnck_window_get_screen(window);
+ WnckWorkspace *workspace = wnck_screen_get_active_workspace(screen);
+
+ if (workspace == NULL) {
+ workspace = wnck_screen_get_workspace(screen, 0);
+ }
+
+ if (workspace == NULL) {
+ g_printerr(_("Could not get workspace"));
+ return 1;
+ }
+
+ geom->x = 0;
+ geom->y = 0;
+ geom->width = wnck_workspace_get_width(workspace);
+ geom->height = wnck_workspace_get_height(workspace);
+
+ return 0;
+}
+
+
+/**
+ * Wrapper for the above geometry-reading functions
+ * Selects according to monitor number
+ * Returns the monitor index, MONITOR_ALL or, on error, MONITOR_NONE
+ */
+int get_monitor_or_workspace_geometry(int monitor_no, WnckWindow *window, GdkRectangle *bounds)
+{
+ int ret;
+
+ switch (monitor_no)
+ {
+ case MONITOR_ALL:
+ return get_window_workspace_geometry(window, bounds) ? MONITOR_NONE : MONITOR_ALL;
+
+ case MONITOR_WINDOW:
+ ret = get_monitor_index_geometry(window, NULL, bounds);
+ return ret < 0 ? MONITOR_NONE : ret;
+
+ default:
+ if (monitor_no < 0 || monitor_no >= get_monitor_count())
+ return MONITOR_NONE;
+ return get_monitor_geometry(monitor_no, bounds) < 0 ? MONITOR_NONE : monitor_no;
+ }
+}
diff --git a/devilspie/xutils.h b/devilspie/xutils.h
new file mode 100644
index 0000000..c567c41
--- /dev/null
+++ b/devilspie/xutils.h
@@ -0,0 +1,85 @@
+/**
+ * This file is part of devilspie2
+ * Copyright (C) 2001 Havoc Pennington, 2011-2019 Andreas Rönnquist
+ *
+ * devilspie2 is free software: you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as published
+ * by the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * devilspie2 is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with devilspie2.
+ * If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef __HEADER_XUTILS_
+#define __HEADER_XUTILS_
+
+#include "compat.h"
+
+/* Special values for specifying which monitor.
+ * These values are relied upon; changing them will require changing the code where used.
+ * Scripts use these numbers plus 1.
+ * Where these are used, values ≥ 0 (> 0 in scripts) correspond to actual monitors.
+ */
+#define MONITOR_NONE INT_MIN
+#define MONITOR_ALL -2 /* Monitor no. -1 (all monitors as one) */
+#define MONITOR_WINDOW -1 /* Monitor no. 0 (current monitor) */
+
+/**
+ *
+ */
+Atom my_wnck_atom_get(const char *atom_name);
+
+void devilspie2_change_state(Screen *screen,
+ Window xwindow,
+ gboolean add,
+ Atom state1,
+ Atom state2);
+
+Screen* devilspie2_window_get_xscreen(Window xid);
+
+void devilspie2_error_trap_push();
+int devilspie2_error_trap_pop();
+
+gboolean decorate_window(Window xid);
+gboolean undecorate_window(Window xid);
+gboolean get_decorated(Window xid);
+
+char* my_wnck_get_string_property(Window xwindow, Atom atom, gboolean *utf8) ATTR_MALLOC;
+void my_wnck_set_string_property(Window xwindow, Atom atom, const gchar *const value, gboolean utf8);
+void my_wnck_set_cardinal_property (Window xwindow, Atom atom, int32_t value);
+void my_wnck_delete_property (Window xwindow, Atom atom);
+
+gboolean my_wnck_get_cardinal_list(Window xwindow,
+ Atom atom,
+ gulong **cardinals,
+ int *len);
+
+int devilspie2_get_viewport_start(Window xwindow, int *x, int *y);
+
+void my_window_set_window_type(Window xid, gchar *window_type);
+void my_window_set_opacity(Window xid, double value);
+
+void adjust_for_decoration(WnckWindow *window, int *x, int *y, int *w, int *h);
+void set_window_geometry(WnckWindow *window, int x, int y, int w, int h, gboolean adjust_for_decoration);
+
+int get_monitor_count(void);
+int get_monitor_index_geometry(WnckWindow *window, const GdkRectangle *window_r, /*out*/ GdkRectangle *monitor_r);
+int get_monitor_geometry(int index, /*out*/ GdkRectangle *monitor_r);
+
+int get_window_workspace_geometry(WnckWindow *window, /*out*/ GdkRectangle *monitor_r);
+
+/*
+ * Wrapper for the above geometry-reading functions
+ * Selects according to monitor number (MONITOR_ALL, MONITOR_WINDOW or a monitor index no.)
+ * Returns the monitor index, MONITOR_ALL or, on error, MONITOR_NONE
+ */
+int get_monitor_or_workspace_geometry(int monitor_no, WnckWindow *window, /*out*/ GdkRectangle *monitor_or_workspace_r);
+
+#endif /*__HEADER_XUTILS_*/
diff --git a/stuff.go b/stuff.go
new file mode 100644
index 0000000..d3e5deb
--- /dev/null
+++ b/stuff.go
@@ -0,0 +1,69 @@
+package main
+
+import (
+ "fmt"
+ "os"
+ "os/exec"
+ "time"
+
+ "github.com/BurntSushi/xgb"
+ "github.com/BurntSushi/xgb/xproto"
+ "go.wit.com/log"
+)
+
+func main() {
+ conn, err := xgb.NewConn()
+ if err != nil {
+ fmt.Println("Failed to connect to X server:", err)
+ os.Exit(1)
+ }
+ defer conn.Close()
+
+ // Start the terminal (replace with your app)
+ go func() {
+ if err := exec.Command("mate-terminal", "--title", "Workspace1-Terminal").Start(); err != nil {
+ fmt.Println("Error starting terminal:", err)
+ }
+ }()
+
+ // Wait for the window to appear
+ time.Sleep(2 * time.Second)
+
+ // Get the root window
+ setup := xproto.Setup(conn)
+ root := setup.DefaultScreen(conn).Root
+
+ // List children windows
+ reply, err := xproto.QueryTree(conn, root).Reply()
+ if err != nil {
+ fmt.Println("Failed to query windows:", err)
+ os.Exit(1)
+ }
+
+ // Find the window with the specified title
+ var target xproto.Window
+ for _, child := range reply.Children {
+ nameReply, err := xproto.GetProperty(conn, false, child,
+ xproto.AtomWmName, xproto.AtomString, 0, (1<<32)-1).Reply()
+ if err != nil || len(nameReply.Value) == 0 {
+ continue
+ }
+
+ name := string(nameReply.Value)
+ log.Info("found name:", name)
+ if name == "Workspace1-Terminal" {
+ target = child
+ break
+ }
+ }
+
+ if target == 0 {
+ fmt.Println("Window not found.")
+ os.Exit(1)
+ }
+
+ // Move the window to workspace 1 and set its geometry
+ xproto.ConfigureWindow(conn, target, xproto.ConfigWindowX|xproto.ConfigWindowY|xproto.ConfigWindowWidth|xproto.ConfigWindowHeight,
+ []uint32{100, 100, 800, 600})
+ fmt.Println("Window moved and resized.")
+}