commit cfd6fdadd756edccd0115563d759e37306b57e9c Author: Jeff Carr Date: Sun Nov 24 06:57:05 2024 -0600 day 1 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 . + */ + +#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 . + */ +#include + +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include + +#include + +#include + + +#include +#include +#include + +#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 . + */ +#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 . + */ + +#include + +#include +#include +#include +#include +#include +#include + +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include + +#include +#include +#include + +#include + +#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; idata; + + 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 . + */ +#include +#include + +#include + +#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 . + */ +#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 . + */ +#ifndef __HEADER_INTL__ +#define __HEADER_INTL__ + +#include +#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 . + */ + + +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include + +#include + +#include + +#include +#include +#include + +#include + +#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 . + */ +#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 . + */ +#include "glib.h" +#include +#include +#include +#include + +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include + +#include +#include + +#include +#include + +#include + +#include + +#include "intl.h" + +#include +#include +#include + +#include + +#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 . + */ + +#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 . + */ +#include + +#include +#include +#include +#include +#include + +// FIXME: retrieve screen position via wnck +#include + +#define WNCK_I_KNOW_THIS_IS_UNSTABLE +#include + +#include + +#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 . + */ + +#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.") +}