|
1 | | -# -*- coding: utf-8 -*- |
| 1 | +# SPDX-FileCopyrightText: 2012-2026 Nils Görs <weechatter@arcor.de> |
2 | 2 | # |
3 | | -# Copyright (c) 2012-2013 by nils_2 <weechatter@arcor.de> |
4 | | -# |
5 | | -# cylce to currently used server if you are using merged server buffer |
6 | | -# |
7 | | -# This program is free software; you can redistribute it and/or modify |
8 | | -# it under the terms of the GNU General Public License as published by |
9 | | -# the Free Software Foundation; either version 3 of the License, or |
10 | | -# (at your option) any later version. |
11 | | -# |
12 | | -# This program is distributed in the hope that it will be useful, |
13 | | -# but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | -# GNU General Public License for more details. |
16 | | -# |
17 | | -# You should have received a copy of the GNU General Public License |
18 | | -# along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 3 | +# SPDX-License-Identifier: GPL-3.0-or-later |
| 4 | + |
| 5 | +# 2012-01-22: nils_2,(freenode.#weechat) |
| 6 | +# 0.1 : initial release |
19 | 7 | # |
20 | | -# 2013-01-25: nils_2, (freenode.#weechat) |
21 | | -# 0.4 : make script compatible with Python 3.x |
| 8 | +# 2012-01-27: nils_2,(freenode.#weechat) |
| 9 | +# 0.2 : fix: bug with split windows removed (reported by meingtsla) |
22 | 10 | # |
23 | 11 | # 2012-01-28: nils_2,(freenode.#weechat) |
24 | 12 | # 0.3 : adapted to bugfix #31158 and new signal hook_signal("window_switch") |
25 | 13 | # |
26 | | -# 2012-01-27: nils_2,(freenode.#weechat) |
27 | | -# 0.2 : fix: bug with split windows removed (reported by meingtsla) |
28 | | -# |
29 | | -# 2012-01-22: nils_2,(freenode.#weechat) |
30 | | -# 0.1 : initial release |
| 14 | +# 2013-01-25: nils_2, (freenode.#weechat) |
| 15 | +# 0.4 : make script compatible with Python 3.x |
31 | 16 | # |
32 | | -# Development is currently hosted at |
33 | | -# https://github.com/weechatter/weechat-scripts |
| 17 | +# 2026-09-11: nils_2, (libera.#weechat) |
| 18 | +# 0.5 : improve script logic and overall stability, with better error handling |
| 19 | +# : add: /help text |
34 | 20 |
|
35 | 21 | try: |
36 | | - import weechat,re |
37 | | - |
38 | | -except Exception: |
| 22 | + import weechat |
| 23 | +except ImportError: |
39 | 24 | print("This script must be run under WeeChat.") |
40 | | - print("Get WeeChat now at: http://www.weechat.org/") |
41 | | - quit() |
| 25 | + print("Get WeeChat now at: https://weechat.org/") |
| 26 | + raise SystemExit(1) |
| 27 | + |
| 28 | +import traceback |
| 29 | + |
| 30 | +SCRIPT_NAME = "server_autoswitch" |
| 31 | +SCRIPT_AUTHOR = "nils_2 <weechatter@arcor.de>" |
| 32 | +SCRIPT_VERSION = "0.5" |
| 33 | +SCRIPT_LICENSE = "GPL3" |
| 34 | +SCRIPT_DESC = "cycle merged server buffers to match the server of the current channel" |
42 | 35 |
|
43 | | -SCRIPT_NAME = "server_autoswitch" |
44 | | -SCRIPT_AUTHOR = "nils_2 <weechatter@arcor.de>" |
45 | | -SCRIPT_VERSION = "0.4" |
46 | | -SCRIPT_LICENSE = "GPL" |
47 | | -SCRIPT_DESC = "cycle to currently used server if you are using merged server buffer" |
48 | 36 |
|
49 | | -look_server = "" |
| 37 | +# ================================[ safety wrapper ]========================== |
| 38 | +def safe_cb(func): |
| 39 | + """Catch any exception in a hook callback and log it to the core buffer |
| 40 | + instead of letting it break the script or leave a merge group mid-cycle.""" |
| 41 | + |
| 42 | + def wrapper(*args, **kwargs): |
| 43 | + try: |
| 44 | + return func(*args, **kwargs) |
| 45 | + except Exception as exc: # noqa: BLE001 - intentionally broad |
| 46 | + try: |
| 47 | + weechat.prints( |
| 48 | + "", |
| 49 | + "%s%s: error in %s: %s" |
| 50 | + % (weechat.prefix("error"), SCRIPT_NAME, func.__name__, exc), |
| 51 | + ) |
| 52 | + weechat.prints("", traceback.format_exc()) |
| 53 | + except Exception: |
| 54 | + pass |
| 55 | + return weechat.WEECHAT_RC_OK |
| 56 | + |
| 57 | + wrapper.__name__ = func.__name__ |
| 58 | + return wrapper |
50 | 59 |
|
| 60 | + |
| 61 | +# ================================[ helpers ]================================= |
| 62 | +def merging_enabled(): |
| 63 | + """Fast pre-check: is server-buffer merging switched on at all? Purely |
| 64 | + an optimization to skip the infolist scan for the (very common) case |
| 65 | + where it's off; the actual decision still happens via buffer numbers.""" |
| 66 | + option = weechat.config_get("irc.look.server_buffer") |
| 67 | + if not option: |
| 68 | + return True # option not found (future WeeChat change?) - fail open |
| 69 | + return weechat.config_string(option) != "independent" |
| 70 | + |
| 71 | + |
| 72 | +def buffers_sharing_number(number): |
| 73 | + """Pointers of every buffer that currently shares this buffer number, |
| 74 | + in the same order WeeChat itself cycles through them. A number held by |
| 75 | + only one buffer means "not merged with anything right now".""" |
| 76 | + members = [] |
| 77 | + infolist = weechat.infolist_get("buffer", "", "") |
| 78 | + if not infolist: |
| 79 | + return members |
| 80 | + try: |
| 81 | + while weechat.infolist_next(infolist): |
| 82 | + if weechat.infolist_integer(infolist, "number") == number: |
| 83 | + buf = weechat.infolist_pointer(infolist, "pointer") |
| 84 | + if buf: |
| 85 | + members.append(buf) |
| 86 | + finally: |
| 87 | + weechat.infolist_free(infolist) |
| 88 | + return members |
| 89 | + |
| 90 | + |
| 91 | +def cycle_to_server_buffer(target_buf): |
| 92 | + """Cycle target_buf's merge group (if it is merged with anything) until |
| 93 | + target_buf itself becomes the active/displayed member.""" |
| 94 | + if not target_buf: |
| 95 | + return |
| 96 | + if weechat.buffer_get_integer(target_buf, "active") == 1: |
| 97 | + return # already showing - nothing to do |
| 98 | + |
| 99 | + number = weechat.buffer_get_integer(target_buf, "number") |
| 100 | + group = buffers_sharing_number(number) |
| 101 | + if len(group) < 2: |
| 102 | + return # not currently merged with anything |
| 103 | + |
| 104 | + # Bounded: never take more steps than the group has members, so a |
| 105 | + # buffer that unexpectedly never becomes active can't loop forever. |
| 106 | + for _ in range(len(group)): |
| 107 | + active_buf = next( |
| 108 | + (buf for buf in group if weechat.buffer_get_integer(buf, "active") == 1), |
| 109 | + None, |
| 110 | + ) |
| 111 | + if active_buf is None: |
| 112 | + return # shouldn't happen, but don't spin on it |
| 113 | + weechat.command(active_buf, "/input switch_active_buffer") |
| 114 | + if weechat.buffer_get_integer(target_buf, "active") == 1: |
| 115 | + return |
| 116 | + |
| 117 | + |
| 118 | +def handle_buffer(buf): |
| 119 | + """If buf is an irc channel/private buffer, bring its server buffer to |
| 120 | + the front of its merge group (if merged).""" |
| 121 | + if not buf: |
| 122 | + return |
| 123 | + if not merging_enabled(): |
| 124 | + return |
| 125 | + if weechat.buffer_get_string(buf, "localvar_plugin") != "irc": |
| 126 | + return |
| 127 | + if weechat.buffer_get_string(buf, "localvar_type") not in ("channel", "private"): |
| 128 | + return |
| 129 | + |
| 130 | + servername = weechat.buffer_get_string(buf, "localvar_server") |
| 131 | + if not servername: |
| 132 | + return |
| 133 | + |
| 134 | + server_buf = weechat.info_get("irc_buffer", servername) |
| 135 | + cycle_to_server_buffer(server_buf) |
| 136 | + |
| 137 | + |
| 138 | +# ================================[ signal callbacks ]========================= |
| 139 | +@safe_cb |
| 140 | +def buffer_switch_cb(data, signal, signal_data): |
| 141 | + handle_buffer(signal_data) |
| 142 | + return weechat.WEECHAT_RC_OK |
| 143 | + |
| 144 | + |
| 145 | +@safe_cb |
51 | 146 | def window_switch_cb(data, signal, signal_data): |
52 | | - bufpointer = weechat.window_get_pointer(signal_data,"buffer") |
53 | | - buffer_switch_cb(data,signal,bufpointer) |
| 147 | + # signal_data is a window pointer here, not a buffer pointer - resolve |
| 148 | + # the buffer that window is actually showing. |
| 149 | + handle_buffer(weechat.window_get_pointer(signal_data, "buffer")) |
54 | 150 | return weechat.WEECHAT_RC_OK |
55 | | -def buffer_switch_cb(data, signal, signal_data): |
56 | | - global look_server |
57 | | - look_server = "" |
58 | | - look_server = weechat.config_string(weechat.config_get("irc.look.server_buffer")) |
59 | | - if look_server == "independent": # server buffer independent? |
60 | | - return weechat.WEECHAT_RC_OK # better remove script, you don't need it. |
61 | | - |
62 | | - if weechat.buffer_get_string(signal_data,'name') != 'weechat': # not weechat core buffer |
63 | | - if (weechat.buffer_get_string(signal_data,'localvar_type') == '') or (weechat.buffer_get_string(signal_data,'localvar_type') == 'server'): |
64 | | - return weechat.WEECHAT_RC_OK |
65 | | - elif weechat.buffer_get_string(signal_data,'name') == 'weechat': |
66 | | - return weechat.WEECHAT_RC_OK |
67 | | - |
68 | | - # buffer is channel or private? |
69 | | - if (weechat.buffer_get_string(signal_data,'localvar_type') == 'channel') or (weechat.buffer_get_string(signal_data,'localvar_type') == 'private'): |
70 | | - bufpointer = weechat.window_get_pointer(weechat.current_window(),"buffer") |
71 | | - servername_from_current_buffer = weechat.buffer_get_string(bufpointer, 'localvar_server') |
72 | | - name = weechat.buffer_get_string(bufpointer, 'name') |
73 | | - server_switch(signal_data,servername_from_current_buffer,name) |
| 151 | + |
| 152 | + |
| 153 | +# ================================[ command ]================================== |
| 154 | +@safe_cb |
| 155 | +def command_cb(data, buffer, args): |
| 156 | + handle_buffer(weechat.current_buffer()) |
74 | 157 | return weechat.WEECHAT_RC_OK |
75 | 158 |
|
76 | | -def server_switch(signal_data,servername_from_current_buffer,name): |
77 | | - global look_server |
78 | | - SERVER = {} |
79 | | - |
80 | | - bufpointer = weechat.window_get_pointer(weechat.current_window(),"buffer") |
81 | | - servername_current_buffer = servername_from_current_buffer |
82 | | - if look_server == "merge_with_core": # merge_with_core |
83 | | - SERVER["weechat"] = "core.weechat" |
84 | | - |
85 | | -# get ALL server buffers and save them |
86 | | - infolist = weechat.infolist_get("buffer","","*server.*") # we are only interest in server-buffers |
87 | | - while weechat.infolist_next(infolist): |
88 | | - bufpointer = weechat.infolist_pointer(infolist,"pointer") |
89 | | - server = weechat.infolist_string(infolist, "name") # full servername (server.<servername>) |
90 | | - servername = weechat.infolist_string(infolist, "short_name") # get servername from server (without prefix "server") |
91 | | - active = weechat.infolist_integer(infolist,"active") |
92 | | - SERVER[servername] = server |
93 | | - if (active == 1) and (servername_current_buffer != servername): # buffer active but not correct server buffer? |
94 | | - weechat.command(bufpointer,"/input switch_active_buffer") # switch server buffer |
95 | | - weechat.infolist_free(infolist) # do not forget to free infolist! |
96 | | - |
97 | | -# switch though all server and stop at server from current buffer |
98 | | - i = 0 |
99 | | - while i <= len(SERVER): |
100 | | - for servername,full_name in list(SERVER.items()): |
101 | | - bufpointer = weechat.buffer_search("irc","%s" % full_name) # search pointer from server buffer |
102 | | - if bufpointer == "": # core buffer |
103 | | - if weechat.buffer_get_integer(weechat.buffer_search_main(),'active') == 1: |
104 | | - weechat.command(weechat.buffer_search_main(),"/input switch_active_buffer") |
105 | | - else: # server buffer! |
106 | | - if (servername != servername_current_buffer) and (weechat.buffer_get_integer(bufpointer,'active') == 1): |
107 | | - weechat.command(bufpointer,"/input switch_active_buffer") |
108 | | - elif (servername == servername_current_buffer) and (weechat.buffer_get_integer(bufpointer,'active') == 1): |
109 | | - i = len(SERVER) |
110 | | - break |
111 | | - i += 1 |
112 | | -# ================================[ main ]=============================== |
| 159 | + |
| 160 | +# ================================[ main ]===================================== |
113 | 161 | if __name__ == "__main__": |
114 | | - if weechat.register(SCRIPT_NAME, SCRIPT_AUTHOR, SCRIPT_VERSION, SCRIPT_LICENSE, SCRIPT_DESC, '', ''): |
115 | | - version = weechat.info_get("version_number", "") or 0 |
116 | | - if int(version) >= 0x00030600: |
117 | | - weechat.hook_signal("buffer_switch","buffer_switch_cb","") |
118 | | - weechat.hook_signal("window_switch","window_switch_cb","") |
119 | | - else: |
120 | | - weechat.prnt("","%s%s %s" % (weechat.prefix("error"),SCRIPT_NAME,": needs version 0.3.6 or higher")) |
| 162 | + if weechat.register( |
| 163 | + SCRIPT_NAME, |
| 164 | + SCRIPT_AUTHOR, |
| 165 | + SCRIPT_VERSION, |
| 166 | + SCRIPT_LICENSE, |
| 167 | + SCRIPT_DESC, |
| 168 | + "", |
| 169 | + "", |
| 170 | + ): |
| 171 | + weechat.hook_command( |
| 172 | + SCRIPT_NAME, |
| 173 | + SCRIPT_DESC, |
| 174 | + "", |
| 175 | + "The script only makes sense if you have merged the server buffers.\n\n" |
| 176 | + "This script has no options. It runs automatically on every " |
| 177 | + "buffer switch and window switch.\n\n" |
| 178 | + "Running /%s manually re-syncs the merged server buffer to " |
| 179 | + "the currently viewed channel right away." % SCRIPT_NAME, |
| 180 | + "", |
| 181 | + "command_cb", |
| 182 | + "", |
| 183 | + ) |
| 184 | + weechat.hook_signal("buffer_switch", "buffer_switch_cb", "") |
| 185 | + weechat.hook_signal("window_switch", "window_switch_cb", "") |
| 186 | + |
| 187 | + # Sync once immediately in case a channel is already open when the |
| 188 | + # script gets (re)loaded. |
| 189 | + handle_buffer(weechat.current_buffer()) |
0 commit comments